From 403b0bde2d241224540367c6aa558259a9f4e6b1 Mon Sep 17 00:00:00 2001 From: Shannon Axelrod Date: Thu, 29 Aug 2019 00:17:07 -0700 Subject: [PATCH] adding coordinate support to SpotFindingResults --- .../intensity_table_coordinates.py | 36 +++++++++++++------ .../test/test_intensity_table_coords.py | 9 ++--- .../spots/DetectPixels/pixel_spot_decoder.py | 6 ++-- starfish/core/spots/DetectSpots/detect.py | 6 ++-- .../DetectSpots/local_search_blob_detector.py | 4 +-- starfish/core/types/_spot_finding_results.py | 22 ++++++++++-- 6 files changed, 58 insertions(+), 25 deletions(-) diff --git a/starfish/core/intensity_table/intensity_table_coordinates.py b/starfish/core/intensity_table/intensity_table_coordinates.py index 52b08b001..bf8e03b63 100644 --- a/starfish/core/intensity_table/intensity_table_coordinates.py +++ b/starfish/core/intensity_table/intensity_table_coordinates.py @@ -1,18 +1,22 @@ -from typing import Callable +from typing import Callable, Hashable, Mapping, Optional import numpy as np import xarray as xr from starfish.core.imagestack.imagestack import ImageStack from starfish.core.intensity_table.intensity_table import IntensityTable -from starfish.core.types import Axes, Coordinates, Features +from starfish.core.types import Axes, Coordinates, Features, SpotFindingResults -def transfer_physical_coords_from_imagestack_to_intensity_table( - image_stack: ImageStack, intensity_table: IntensityTable +def transfer_physical_coords_to_intensity_table( + *, + intensity_table: IntensityTable, + image_stack: Optional[ImageStack] = None, + spots: Optional[SpotFindingResults] = None, ) -> IntensityTable: """ - Transfers physical coordinates from an Imagestack's coordinates xarray to an intensity table + Transfers physical coordinates from either an Imagestack or SpotFindingResults to an + intensity table 1. Creates three new coords on the intensity table (xc, yc, zc) 2. For every spot: @@ -20,13 +24,25 @@ def transfer_physical_coords_from_imagestack_to_intensity_table( - Calculate the physical x,y values - Assign those values to the coords arrays for this spot """ - # TODO shanaxel42 consider refactoring pixel case where were can just reshape from Imagestack pairs = ( (Axes.X.value, Coordinates.X.value), (Axes.Y.value, Coordinates.Y.value), (Axes.ZPLANE.value, Coordinates.Z.value) ) + if bool(image_stack) == bool(spots): + raise ValueError("Must provide either SpotFindingResults or ImageStack to calculate " + "coordinates from, and not both.") + + coord_ranges: Mapping[Hashable, xr.DataArray] + if image_stack is not None: + coord_ranges = { + Axes.X.value: image_stack.xarray[Coordinates.X.value], + Axes.Y.value: image_stack.xarray[Coordinates.Y.value], + Axes.ZPLANE.value: image_stack.xarray[Coordinates.Z.value] + } + elif spots is not None: + coord_ranges = spots.physical_coord_ranges # make sure the intensity table gets empty metadata if there are no intensities if intensity_table.sizes[Features.AXIS] == 0: @@ -35,15 +51,15 @@ def transfer_physical_coords_from_imagestack_to_intensity_table( return intensity_table for axis, coord in pairs: - imagestack_pixels: xr.DataArray = image_stack.xarray[axis] + pixels: xr.DataArray = coord_ranges[axis] intensity_table_pixel_offsets: np.ndarray = intensity_table[axis].values # can't interpolate if the axis size == 1, so just select in that case. coordinate_fetcher: Callable - if len(imagestack_pixels) == 1: - coordinate_fetcher = imagestack_pixels.sel + if len(pixels) == 1: + coordinate_fetcher = pixels.sel else: - coordinate_fetcher = imagestack_pixels.interp + coordinate_fetcher = pixels.interp coordinates = coordinate_fetcher({axis: intensity_table_pixel_offsets})[coord] intensity_table[coord] = xr.DataArray(coordinates.values, dims='features') diff --git a/starfish/core/intensity_table/test/test_intensity_table_coords.py b/starfish/core/intensity_table/test/test_intensity_table_coords.py index 5554f02ed..e5f6583ba 100644 --- a/starfish/core/intensity_table/test/test_intensity_table_coords.py +++ b/starfish/core/intensity_table/test/test_intensity_table_coords.py @@ -11,7 +11,7 @@ from .factories import synthetic_decoded_intenisty_table from ..intensity_table import IntensityTable from ..intensity_table_coordinates import ( - transfer_physical_coords_from_imagestack_to_intensity_table, + transfer_physical_coords_to_intensity_table, ) NUMBER_SPOTS = 10 @@ -46,7 +46,8 @@ def test_tranfering_physical_coords_to_intensity_table(): n_spots=NUMBER_SPOTS ) - intensities = transfer_physical_coords_from_imagestack_to_intensity_table(stack, intensities) + intensities = transfer_physical_coords_to_intensity_table(intensity_table=intensities, + image_stack=stack) # Assert that new cords were added xc = intensities.coords[Coordinates.X] @@ -100,8 +101,8 @@ def test_tranfering_physical_coords_to_expression_matrix(): n_spots=NUMBER_SPOTS ) - intensities = transfer_physical_coords_from_imagestack_to_intensity_table( - stack, decoded_intensities) + intensities = transfer_physical_coords_to_intensity_table( + image_stack=stack, intensity_table=decoded_intensities) # Check that error is thrown before target assignment try: diff --git a/starfish/core/spots/DetectPixels/pixel_spot_decoder.py b/starfish/core/spots/DetectPixels/pixel_spot_decoder.py index d9215a77f..9b4724fbc 100644 --- a/starfish/core/spots/DetectPixels/pixel_spot_decoder.py +++ b/starfish/core/spots/DetectPixels/pixel_spot_decoder.py @@ -5,7 +5,7 @@ from starfish.core.imagestack.imagestack import ImageStack from starfish.core.intensity_table.intensity_table import IntensityTable from starfish.core.intensity_table.intensity_table_coordinates import \ - transfer_physical_coords_from_imagestack_to_intensity_table + transfer_physical_coords_to_intensity_table from ._base import DetectPixelsAlgorithm from .combine_adjacent_features import CombineAdjacentFeatures, ConnectedComponentDecodingResult @@ -85,6 +85,6 @@ def run( decoded_spots, image_decoding_results = caf.run(intensities=decoded_intensities, n_processes=n_processes) - transfer_physical_coords_from_imagestack_to_intensity_table(image_stack=primary_image, - intensity_table=decoded_spots) + transfer_physical_coords_to_intensity_table(image_stack=primary_image, + intensity_table=decoded_spots) return decoded_spots, image_decoding_results diff --git a/starfish/core/spots/DetectSpots/detect.py b/starfish/core/spots/DetectSpots/detect.py index 2349c3940..2b347100f 100644 --- a/starfish/core/spots/DetectSpots/detect.py +++ b/starfish/core/spots/DetectSpots/detect.py @@ -9,7 +9,7 @@ from starfish.core.imagestack.imagestack import ImageStack from starfish.core.intensity_table.intensity_table import IntensityTable from starfish.core.intensity_table.intensity_table_coordinates import \ - transfer_physical_coords_from_imagestack_to_intensity_table + transfer_physical_coords_to_intensity_table from starfish.core.types import Axes, Features, Number, SpotAttributes @@ -242,7 +242,7 @@ def detect_spots(data_stack: ImageStack, ) intensity_table = concatenate_spot_attributes_to_intensities(spot_attributes_list) - transfer_physical_coords_from_imagestack_to_intensity_table(image_stack=data_stack, - intensity_table=intensity_table) + transfer_physical_coords_to_intensity_table(image_stack=data_stack, + intensity_table=intensity_table) return intensity_table diff --git a/starfish/core/spots/DetectSpots/local_search_blob_detector.py b/starfish/core/spots/DetectSpots/local_search_blob_detector.py index 3ad35e412..81042aff8 100644 --- a/starfish/core/spots/DetectSpots/local_search_blob_detector.py +++ b/starfish/core/spots/DetectSpots/local_search_blob_detector.py @@ -19,7 +19,7 @@ from starfish.core.imagestack.imagestack import ImageStack from starfish.core.intensity_table.intensity_table import IntensityTable from starfish.core.intensity_table.intensity_table_coordinates import \ - transfer_physical_coords_from_imagestack_to_intensity_table + transfer_physical_coords_to_intensity_table from starfish.core.types import Axes, Features, Number, SpotAttributes from ._base import DetectSpotsAlgorithm @@ -435,7 +435,7 @@ def run( anchor_round=self.anchor_round ) - transfer_physical_coords_from_imagestack_to_intensity_table( + transfer_physical_coords_to_intensity_table( image_stack=primary_image, intensity_table=intensity_table ) diff --git a/starfish/core/types/_spot_finding_results.py b/starfish/core/types/_spot_finding_results.py index b1ff5f8b4..5df9dc16f 100644 --- a/starfish/core/types/_spot_finding_results.py +++ b/starfish/core/types/_spot_finding_results.py @@ -1,6 +1,9 @@ -from typing import List, Mapping, MutableMapping, Optional, Tuple +from typing import Hashable, List, Mapping, MutableMapping, Optional, Tuple + +import xarray as xr + +from starfish.core.types import Axes, Coordinates, SpotAttributes -from starfish.core.types import Axes, SpotAttributes AXES_ORDER = (Axes.ROUND, Axes.CH) @@ -12,7 +15,7 @@ class SpotFindingResults: SpotAttributes. """ - def __init__(self, spot_attributes_list: Optional[List[Tuple]] = None): + def __init__(self, imagestack_coords, spot_attributes_list: Optional[List[Tuple]] = None): """ Construct a SpotFindingResults instance @@ -28,6 +31,10 @@ def __init__(self, spot_attributes_list: Optional[List[Tuple]] = None): indices: spots for indices, spots in spot_attributes_list } + self.physical_coord_ranges: Mapping[Hashable, xr.DataArray] = { + Axes.X.value: imagestack_coords[Coordinates.X.value], + Axes.Y.value: imagestack_coords[Coordinates.Y.value], + Axes.ZPLANE.value: imagestack_coords[Coordinates.Z.value]} def __setitem__(self, indices: Mapping[Axes, int], spots: SpotAttributes): """ @@ -83,4 +90,13 @@ def ch_labels(self): """ Return the set of ch labels in the SpotFindingResults """ + return sorted(set(ch for (r, ch) in self.keys())) + + @property + def get_physical_coord_ranges(self) -> Mapping[Hashable, xr.DataArray]: + """ + Returns the physical coordinate ranges the SpotResults cover. Needed + information for calculating the physical coordinate values of decoded spots. + """ + return self.physical_coord_ranges