Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tasks to max project across channels or z-slices #8

Merged
merged 20 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions merlin/analysis/decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def __init__(
self.parameters["z_duplicate_xy_pixel_threshold"] = np.sqrt(2)

self.cropWidth = self.parameters["crop_width"]
self.imageSize = dataSet.get_image_dimensions()
self.imageSize = dataSet.imageDimensions

def fragment_count(self):
return len(self.dataSet.get_fovs())
Expand Down Expand Up @@ -106,7 +106,7 @@ def _run_analysis(self, fragmentIndex):

zPositionCount = len(self.dataSet.get_z_positions())
bitCount = codebook.get_bit_count()
imageShape = self.dataSet.get_image_dimensions()
imageShape = self.dataSet.imageDimensions
decodedImages = np.zeros((zPositionCount, *imageShape), dtype=np.int16)
magnitudeImages = np.zeros((zPositionCount, *imageShape), dtype=np.float32)
distances = np.zeros((zPositionCount, *imageShape), dtype=np.float32)
Expand Down
2 changes: 1 addition & 1 deletion merlin/analysis/generatemosaic.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
self.parameters["draw_fov_labels"] = False

if self.parameters["microns_per_pixel"] == "full_resolution":
self.mosaicMicronsPerPixel = self.dataSet.get_microns_per_pixel()
self.mosaicMicronsPerPixel = self.dataSet.micronsPerPixel

Check warning on line 29 in merlin/analysis/generatemosaic.py

View check run for this annotation

Codecov / codecov/patch

merlin/analysis/generatemosaic.py#L29

Added line #L29 was not covered by tests
else:
self.mosaicMicronsPerPixel = self.parameters["microns_per_pixel"]

Expand Down
10 changes: 5 additions & 5 deletions merlin/analysis/globalalign.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@

def fov_coordinates_to_global(self, fov, fovCoordinates):
fovStart = self.dataSet.get_fov_offset(fov)
micronsPerPixel = self.dataSet.get_microns_per_pixel()
micronsPerPixel = self.dataSet.micronsPerPixel
if len(fovCoordinates) == 2:
return (
fovStart[0] + fovCoordinates[0] * micronsPerPixel,
Expand Down Expand Up @@ -185,7 +185,7 @@
return pixels

def fov_to_global_transform(self, fov):
micronsPerPixel = self.dataSet.get_microns_per_pixel()
micronsPerPixel = self.dataSet.micronsPerPixel
globalStart = self.fov_coordinates_to_global(fov, (0, 0))

return np.float32(
Expand All @@ -197,7 +197,7 @@
)

def get_global_extent(self):
fovSize = self.dataSet.get_image_dimensions()
fovSize = self.dataSet.imageDimensions
fovBounds = [
self.fov_coordinates_to_global(x, (0, 0)) for x in self.dataSet.get_fovs()
] + [
Expand Down Expand Up @@ -261,8 +261,8 @@
def _get_overlapping_regions(self, fov: int, minArea: int = 2000):
"""Get a list of all the fovs that overlap with the specified fov."""
positions = self.dataSet.get_stage_positions()
pixelToMicron = self.dataSet.get_microns_per_pixel()
fovMicrons = [x * pixelToMicron for x in self.dataSet.get_image_dimensions()]
pixelToMicron = self.dataSet.micronsPerPixel
fovMicrons = [x * pixelToMicron for x in self.dataSet.imageDimensions]

Check warning on line 265 in merlin/analysis/globalalign.py

View check run for this annotation

Codecov / codecov/patch

merlin/analysis/globalalign.py#L264-L265

Added lines #L264 - L265 were not covered by tests
fovPosition = positions.loc[fov]
overlapAreas = [
i
Expand Down
160 changes: 160 additions & 0 deletions merlin/analysis/maxproject.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import re
from abc import abstractmethod
from typing import List

import numpy as np

from merlin.core import analysistask


class MaxProject(analysistask.ParallelAnalysisTask):
"""An abstract class to max project a set of channel images into a new channel."""

def __init__(self, dataSet, parameters=None, analysisName=None) -> None:
super().__init__(dataSet, parameters, analysisName)
if "write_images" not in self.parameters:
self.parameters["write_images"] = False

Check warning on line 16 in merlin/analysis/maxproject.py

View check run for this annotation

Codecov / codecov/patch

merlin/analysis/maxproject.py#L16

Added line #L16 was not covered by tests

self.write_images = self.parameters["write_images"]

@property
def z_positions(self) -> List[float]:
return self.dataSet.get_z_positions()

@property
def channels(self) -> np.ndarray:
return self.dataSet.get_data_organization().get_data_channels()

@property
def channel_names(self) -> np.ndarray:
return self.dataSet.get_data_organization().data["channelName"]

def get_image(self, channel, fov, z):
"""Get an image for a specific channel, fov, and z position."""
return self.dataSet.get_raw_image(

Check warning on line 34 in merlin/analysis/maxproject.py

View check run for this annotation

Codecov / codecov/patch

merlin/analysis/maxproject.py#L34

Added line #L34 was not covered by tests
channel,
fov,
# TODO: make z_index_to_position internal to dataSet?
self.dataSet.z_index_to_position(z),
)

def get_images(self, fov: int) -> np.ndarray:
"""Get a set of images for the specified fov.

Args:
fov: index of the field of view

Returns:
A 4-dimensional numpy array containing the images for the fov.
The images are arranged as [channel, z, x, y].
"""
return np.array(
[
[self.get_image(c, fov, z) for z in self.z_positions]
for c in self.channels
]
)

@abstractmethod
def _run_analysis(self, fov):
# This analysis task does not need computation
pass

Check warning on line 61 in merlin/analysis/maxproject.py

View check run for this annotation

Codecov / codecov/patch

merlin/analysis/maxproject.py#L61

Added line #L61 was not covered by tests


class MaxProjectFiducial(MaxProject):
"""Max projects all z-slices of a set of fiducial images into a single image."""

def __init__(self, dataSet, parameters=None, analysisName=None) -> None:
super().__init__(dataSet, parameters, analysisName)

def fragment_count(self):
return len(self.dataSet.get_fovs())

def get_estimated_memory(self):
return 2048

Check warning on line 74 in merlin/analysis/maxproject.py

View check run for this annotation

Codecov / codecov/patch

merlin/analysis/maxproject.py#L74

Added line #L74 was not covered by tests

def get_estimated_time(self):
return 5

Check warning on line 77 in merlin/analysis/maxproject.py

View check run for this annotation

Codecov / codecov/patch

merlin/analysis/maxproject.py#L77

Added line #L77 was not covered by tests

def get_dependencies(self):
return []

def get_image(self, channel: int, fov: int, z: int) -> np.ndarray:
"""Get an image for a specific channel, fov, and z position."""
return self.dataSet.get_fiducial_image(channel, fov, z)

def get_images(self, fov: int) -> np.ndarray:
images = super().get_images(fov)
projected = np.max(images, axis=1)
return projected

def get_fiducial_image(self, channel: int, fov: int) -> np.ndarray:
return self.get_images(fov)[channel]

def _run_analysis(self, fov):
projected = self.get_images(fov)
if self.write_images:
metadata = self.dataSet.analysis_tiff_description(1, len(self.channels))

with self.dataSet.writer_for_analysis_images(
self, "max_projected_fiducial_beads", fov
) as f:
for channel in range(len(self.channels)):
f.save(
projected[channel], photometric="MINISBLACK", metadata=metadata
)


class MaxProjectBits(MaxProject):
"""Combines all bit channels into a polyT channel."""

def __init__(self, dataSet, parameters=None, analysisName=None) -> None:
super().__init__(dataSet, parameters, analysisName)

if "channel_regex" not in self.parameters:
self.parameters["channel_regex"] = ""

Check warning on line 115 in merlin/analysis/maxproject.py

View check run for this annotation

Codecov / codecov/patch

merlin/analysis/maxproject.py#L115

Added line #L115 was not covered by tests

self.channel_regex = self.parameters["channel_regex"]

def fragment_count(self):
return len(self.dataSet.get_fovs())

def get_estimated_memory(self):
return 2048

Check warning on line 123 in merlin/analysis/maxproject.py

View check run for this annotation

Codecov / codecov/patch

merlin/analysis/maxproject.py#L123

Added line #L123 was not covered by tests

def get_estimated_time(self):
return 1

Check warning on line 126 in merlin/analysis/maxproject.py

View check run for this annotation

Codecov / codecov/patch

merlin/analysis/maxproject.py#L126

Added line #L126 was not covered by tests

def get_dependencies(self):
return [self.parameters["warp_task"]]

def get_image(self, channel: int, fov: int, z: int) -> np.ndarray:
warp_task = self.dataSet.load_analysis_task(self.parameters["warp_task"])
z_index = self.dataSet.position_to_z_index(z)
return warp_task.get_aligned_image(fov, channel, z_index)

def get_images(self, fov: int) -> np.ndarray:
"""Get the max projected bit images for the specified fov.

Args:
fov (int): index of the field of view

Returns:
np.ndarray: Image array of shape (z, x, y) with the projected bits.
"""
images = super().get_images(fov)
is_bit = np.array(
[re.match(self.channel_regex, name) for name in self.channel_names],
dtype=bool,
)
bit_images = images[is_bit]
projected = np.max(bit_images, axis=0)
return projected

def _run_analysis(self, fov):
projected = self.get_images(fov)
if self.write_images:
metadata = self.dataSet.analysis_tiff_description(len(self.z_positions), 1)
with self.dataSet.writer_for_analysis_images(self, "polyT", fov) as f:
for z in range(len(self.z_positions)):
f.save(projected[z], photometric="MINISBLACK", metadata=metadata)
4 changes: 1 addition & 3 deletions merlin/analysis/optimize.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@


class OptimizeIteration(decode.BarcodeSavingParallelAnalysisTask):
"""An analysis task for performing a single iteration of scale factor
optimization.
"""
"""Performs a single iteration of scale factor optimization."""

def __init__(self, dataSet, parameters=None, analysisName=None) -> None:
super().__init__(dataSet, parameters, analysisName)
Expand Down
Loading