def run( self, spots: SpotFindingResults, *args ) -> DecodedIntensityTable: """Decode spots by selecting the max-valued channel in each sequencing round Parameters ---------- spots : SpotFindingResults A Dict of tile indices and their corresponding measured spots Returns ------- DecodedIntensityTable : IntensityTable decoded and appended with Features.TARGET and Features.QUALITY values. """ intensities = self.trace_builder(spot_results=spots, anchor_round=self.anchor_round, search_radius=self.search_radius) transfer_physical_coords_to_intensity_table(intensity_table=intensities, spots=spots) return self.codebook.decode_metric( intensities, max_distance=self.max_distance, min_intensity=self.min_intensity, norm_order=self.norm_order, metric=self.metric, return_original_intensities=self.return_original_intensities )
def run( self, primary_image: ImageStack, n_processes: Optional[int] = None, *args, ) -> Tuple[IntensityTable, ConnectedComponentDecodingResult]: """decode pixels and combine them into spots using connected component labeling Parameters ---------- primary_image : ImageStack ImageStack containing spots n_processes : Optional[int] The number of processes to use for CombineAdjacentFeatures. If None, uses the output of os.cpu_count() (default = None). Returns ------- IntensityTable : IntensityTable containing decoded spots ConnectedComponentDecodingResult : Results of connected component labeling """ pixel_intensities = IntensityTable.from_image_stack(primary_image) decoded_intensities = self.codebook.decode_metric( pixel_intensities, max_distance=self.distance_threshold, min_intensity=self.magnitude_threshold, norm_order=self.norm_order, metric=self.metric) caf = CombineAdjacentFeatures(min_area=self.min_area, max_area=self.max_area, mask_filtered_features=True) decoded_spots, image_decoding_results = caf.run( intensities=decoded_intensities, n_processes=n_processes) transfer_physical_coords_to_intensity_table( image_stack=primary_image, intensity_table=decoded_spots) return decoded_spots, image_decoding_results
def run(self, spots: SpotFindingResults, n_processes: int=1, *args) -> DecodedIntensityTable: """ Decode spots by finding the set of nonoverlapping barcodes that have the minimum spatial variance within each barcode Parameters ---------- spots: SpotFindingResults A Dict of tile indices and their corresponding measured spots n_processes: int Number of threads to run decoder in parallel with Returns ------- DecodedIntensityTable : IntensityTable decoded and appended with Features.TARGET and Features.QUALITY values. """ # Rename n_processes (trying to stay consistent between starFISH's _ variables and my # camel case ones) numJobs = n_processes # If using an search radius exactly equal to a possible distance between two pixels # (ex: 1), some distances will be calculated as slightly less than their exact distance # (either due to rounding or precision errors) so search radius needs to be slightly # increased to ensure this doesn't happen self.searchRadius += 0.001 # Create dictionary where keys are round labels and the values are pandas dataframes # containing information on the spots found in that round spotTables = _merge_spots_by_round(spots) # Add one to channels labels (prevents collisions between hashes of barcodes later) for r in spots.round_labels: spotTables[r]['c'] += 1 # Set list of round omission numbers to loop through roundOmits = range(self.errorRounds + 1) # Decode for each round omission number, store results in allCodes table allCodes = pd.DataFrame() for currentRoundOmitNum in roundOmits: # Create necessary reference dictionaries neighborDict, channelDict, spotCoords = createRefDicts(spotTables, self.searchRadius) # Chooses best barcode for all spots in each round sequentially (possible barcode # space can become quite large which can increase memory needs so I do it this way so # we only need to store all potential barcodes that originate from one round at a # time) decodedTables = {} for r in range(len(spotTables)): roundData = deepcopy(spotTables[r]) roundData = roundData.drop(['intensity', 'z', 'y', 'x', 'radius', 'c'], axis=1) roundData.index += 1 # Create dictionary of dataframes (based on spotTables data) that contains # additional columns for each spot containing all the possible barcodes that # could be constructed from the neighbors of that spot roundData = buildBarcodes(roundData, neighborDict, currentRoundOmitNum, channelDict, r, numJobs) # Match possible barcodes to codebook and add new columns with info about barcodes # that had a codebook match roundData = decoder(roundData, self.codebook, currentRoundOmitNum, r, numJobs) # Choose most likely barcode for each spot in each round by find the possible # decodable barcode with the least spatial variance between the spots that made up # the barcode roundData = distanceFilter(roundData, spotCoords, r, numJobs) # Assign to DecodedTables dictionary decodedTables[r] = roundData # Only do the following if barcodes were founds totalSpots = sum([len(decodedTables[table]) for table in decodedTables]) if totalSpots: # Turn spot table dictionary into single table, filter barcodes by round frequency, # add additional information, and choose between barcodes that have overlapping # spots finalCodes = cleanup(decodedTables, spotCoords, channelDict) # If this is not the last round omission number to run, remove spots that have just # been found to be in passing barcodes from spotTables so they are not used for the # next round omission number if currentRoundOmitNum != roundOmits[-1]: spotTables = removeUsedSpots(finalCodes, spotTables) # Append found codes to allCodes table allCodes = allCodes.append(finalCodes).reset_index(drop=True) # Create and fill in intensity table channels = spots.ch_labels rounds = spots.round_labels # create empty IntensityTable filled with np.nan data = np.full((len(allCodes), len(rounds), len(channels)), fill_value=np.nan) dims = (Features.AXIS, Axes.ROUND.value, Axes.CH.value) centers = allCodes['center'] coords: Mapping[Hashable, Tuple[str, Any]] = { Features.SPOT_RADIUS: (Features.AXIS, np.full(len(allCodes), 1)), Axes.ZPLANE.value: (Features.AXIS, np.asarray([round(c[2]) for c in centers])), Axes.Y.value: (Features.AXIS, np.asarray([round(c[1]) for c in centers])), Axes.X.value: (Features.AXIS, np.asarray([round(c[0]) for c in centers])), Features.SPOT_ID: (Features.AXIS, np.arange(len(allCodes))), Features.AXIS: (Features.AXIS, np.arange(len(allCodes))), Axes.ROUND.value: (Axes.ROUND.value, rounds), Axes.CH.value: (Axes.CH.value, channels) } int_table = IntensityTable(data=data, dims=dims, coords=coords) # Fill in data values table_codes = [] for i in range(len(allCodes)): code = [] for ch in allCodes.loc[i, 'best_barcodes']: # If a round is not used, row will be all zeros code.append(np.asarray([0 if j != ch else 1 for j in range(len(channels))])) table_codes.append(np.asarray(code)) int_table.values = np.asarray(table_codes) int_table = transfer_physical_coords_to_intensity_table(intensity_table=int_table, spots=spots) # Validate results are correct shape self.codebook._validate_decode_intensity_input_matches_codebook_shape(int_table) # Create DecodedIntensityTable result = DecodedIntensityTable.from_intensity_table( int_table, targets=(Features.AXIS, allCodes['best_targets'].astype('U')), distances=(Features.AXIS, allCodes["best_distances"]), passes_threshold=(Features.AXIS, np.full(len(allCodes), True)), rounds_used=(Features.AXIS, allCodes['rounds_used'])) return result
def run( self, primary_image: ImageStack, blobs_image: Optional[ImageStack] = None, blobs_axes: Optional[Tuple[Axes, ...]] = None, verbose: bool = False, n_processes: Optional[int] = None, *args, ) -> IntensityTable: """Find 1-hot coded spots in data. Parameters ---------- data : ImageStack Image data containing coded spots. verbose : bool If True, report on progress of spot finding. n_processes : Optional[int] Number of processes to devote to spot finding. If None, will use the number of available cpus (Default None). Notes ----- blobs_image is an unused parameter that is included for testing purposes. It should not be passed to this method. If it is passed, the method will trigger a ValueError. Returns ------- IntensityTable Contains detected coded spots. """ DeprecationWarning( "Starfish is embarking on a SpotFinding data structures refactor" "(See https://github.com/spacetx/starfish/issues/1514) This version of " "LocalSearchBlobDetector will soon be deleted. To find and decode your" " spots please instead use FindSpots.BlobDetector then " "DecodeSpots.PerRoundMaxChannel with the parameter " "TraceBuildingStrategies.NEAREST_NEIGHBOR. See example in STARmap.py" ) if blobs_image is not None: raise ValueError( "blobs_image shouldn't be set for LocalSearchBlobDetector. This is likely a usage " "error.") per_tile_spot_results = self._find_spots(primary_image, verbose=verbose, n_processes=n_processes) per_round_spot_results = self._merge_spots_by_round( per_tile_spot_results) distances, indices = self._match_spots( per_round_spot_results, search_radius=self.search_radius, anchor_round=self.anchor_round) # TODO implement consensus seeding (SeqFISH) intensity_table = self._build_intensity_table( per_round_spot_results, distances, indices, rounds=primary_image.xarray[Axes.ROUND.value].values, channels=primary_image.xarray[Axes.CH.value].values, search_radius=self.search_radius, anchor_round=self.anchor_round) transfer_physical_coords_to_intensity_table( image_stack=primary_image, intensity_table=intensity_table) return intensity_table
def detect_spots( data_stack: ImageStack, spot_finding_method: Callable[..., SpotAttributes], spot_finding_kwargs: Dict = None, reference_image: Optional[ImageStack] = None, reference_image_max_projection_axes: Optional[Tuple[Axes, ...]] = None, measurement_function: Callable[[Union[np.ndarray, xr.DataArray]], Number] = np.max, radius_is_gyration: bool = False, n_processes: Optional[int] = None) -> IntensityTable: """Apply a spot_finding_method to a ImageStack Parameters ---------- data_stack : ImageStack The ImageStack containing spots spot_finding_method : Callable[..., IntensityTable] The method to identify spots spot_finding_kwargs : Dict additional keyword arguments to pass to spot_finding_method reference_image : xr.DataArray (Optional) a reference image. If provided, spots will be found in this image, and then the locations that correspond to these spots will be measured across each channel and round, filling in the values in the IntensityTable reference_image_max_projection_axes : Tuple[Axes] Generate the reference image by max-projecting reference_image across these axes. measurement_function : Callable[[Union[np.ndarray, xr.DataArray]], Number] the function to apply over the spot area to extract the intensity value (default 'np.max') radius_is_gyration : bool if True, indicates that the radius corresponds to radius of gyration, which is a function of spot intensity, but typically is a smaller unit than the sigma generated by blob_log. In this case, the spot's bounding box is rounded up instead of down when measuring intensity. (default False) is_volume: bool If True, pass 3d volumes (x, y, z) to func, else pass 2d tiles (x, y) to func. (default True) n_processes : Optional[int] The number of processes to use in stack.transform if reference image is None. If None, uses the output of os.cpu_count() (default = None). Notes ----- - This class will always detect spots in 3d. If 2d spot detection is desired, the data should be projected down to "fake 3d" prior to submission to this function - If neither reference_image nor reference_from_max_projection are passed, spots will be detected _independently_ in each channel. This assumes a non-multiplex imaging experiment, as only one (ch, round) will be measured for each spot. Returns ------- IntensityTable : IntensityTable containing the intensity of each spot, its radius, and location in pixel coordinates """ if spot_finding_kwargs is None: spot_finding_kwargs = {} if reference_image is not None: if reference_image_max_projection_axes is not None: reference_image = reference_image.reduce( reference_image_max_projection_axes, func="max") data_image = reference_image._squeezed_numpy( *reference_image_max_projection_axes) else: data_image = reference_image.xarray reference_spot_locations = spot_finding_method(data_image, **spot_finding_kwargs) intensity_table = measure_spot_intensities( data_image=data_stack, spot_attributes=reference_spot_locations, measurement_function=measurement_function, radius_is_gyration=radius_is_gyration, ) else: # don't use a reference image, measure each spot_finding_method = partial(spot_finding_method, **spot_finding_kwargs) spot_attributes_list = data_stack.transform( func=spot_finding_method, group_by={Axes.ROUND, Axes.CH}, n_processes=n_processes) intensity_table = concatenate_spot_attributes_to_intensities( spot_attributes_list) transfer_physical_coords_to_intensity_table( image_stack=data_stack, intensity_table=intensity_table) return intensity_table