Open ambrosejcarr opened 5 years ago
@shanaxel42 patiently discussed this with me yesterday and based on that, I think the best approach here is to use the segmentation mask to select the pixels corresponding to spots from the IntensityTable. Rather than making a monolithic filter component, I think we can make/modify a few components, which will give users more flexibility in the future.
ImageStack
, im_stack
that is fully registered and filtered as desired. Each channel corresponds to a nucleotide.ImageStack
s without a nuclei image. The resulting segmentation mask is an xarray with coordinates/dims that match im_stack
# Sum along the round and channel axes. (To do 2)
proj_stack = im_stack.project('sum', ('Axes.ROUND, Axes.CH'))
# Segment (To do 3)
seg = Segment.Watershed()
masks = seg.run(proj_stack)
# Make a single label image (xarray) from the SegmentationMaskCollection (to do 4)
single_mask = mask.single_image()
# Make the IntensityTable
it = IntensityTable.from_image_stack(im_stack)
# Use the segmentation mask to select the pixels corresponding to spots
it_masked = it.where(single_mask, drop=True)
# Decode it_masked with a pixel-wise decoder
SegmentedMaskCollection
it_masked = it.where(single_mask, drop=True)
works. Segment.Watershed
such that it can accept other things for seeds than an image of nuclei.SegmentedMaskCollection
Thoughts, @ambrosejcarr, @dganguli ?
I like this. One concern I have is how this will function for large fields of view that are dense in z, for example expansion microscopy, whose fields of view are (4, 4, 300, 2048, 2048).
For these images, it would be great to be able to mask individual volumes (per round) and combine the results, as this would substantially reduce memory usage and make processing more parallel.
I outline my best guess at this workflow in #876.
How would the solution proposed here interact with the goals I suggest in #876?
I agree that that the process described above wouldn't work well with acquisitions densely sampled in Z. However, I think the general approach is totally compatible by just performing the operations on a per-round basis. In terms of the operations, it is the same as if it were done in a monolithic pipeline component. However, it would require the user to write a bit more code (e.g.,, some sort of loop or multiprocessing map).
For example, if you are okay with doing the spot segmentation on a per-round basis (I think this is the same as the approach you described in #876):
Alternatively, if you'd like to perform the segmentation on all rounds:
I think your proposed implementation makes sense. This looks good to implement to me. Making sure I'm understanding, this will entail:
For 1 & 3 to work, I think the base calling filter should optionally take a codebook and translate it to the new base-called feature space. I don't think we did that work yet, right?
Hey @ambrosejcarr ! Sorry it's taken so long. I ended up needing to dig into the PixelSpotDetector more than I thought. In short, just masking the ImageStack
or IntensityTable
and feeding it into the PixelSpotDetector
is not straightforward because CombineAdjacentFeatures
reconstructs an image by reshaping the decoded IntensityTable
. Thus, non-rectangular masks don't work. Also, the pixel coordinates for the spots are determined from the reshaped image (a numpy array) and not the IntensityTable
, so the pixel coordinates of the masked features are not preserved.
I propose we do one of the following:
IntensityTable
before decoding, but place them back into the full FOV volume in the decoded_image
. This would eliminate decoding of masked out pixels, but the connected components analysis would be carried out on the full image volume, so I'm not sure it would get the full speedup.SegmentationMaskCollection
and perform pixel wise decoding on each mask region. We would iterate through the segmentation mask collection and perform pixel-wise decoding on the selected pixels in each mask region, then concatenate the features into an IntensityTable
. This would ensure compatibility with the starfish segementation components and allow users to generate their own masks.It seems like (1) is only useful if you need something really fast just to get started. Options (2) and (3) seem pretty similar and I think option (3) probably depends on option (2). Therefore, I think we should do option (2) now and then add (3) in a later PR if needed. I am also open to other approaches. Thoughts?
The Expansion Microscopy Pipeline uses a filter to mask pixels that are ineligible for spot finding. This filter:
We need to implement this filter to support expansion microscopy.
Note: because this reduces the overall pixels eligible for decoding, it may alleviate memory/cpu requirements in #1282