def image_to_spots( self, data_image: np.ndarray, ) -> PerImageSliceSpotResults: """ Find spots using a gaussian blob finding algorithm Parameters ---------- data_image : np.ndarray image containing spots to be detected Returns ------- PerImageSpotResults : includes a SpotAttributes DataFrame of metadata containing the coordinates, intensity and radius of each spot, as well as any extra information collected during spot finding. """ spot_finding_args = { "min_sigma": self.min_sigma, "max_sigma": self.max_sigma, "threshold": self.threshold, "exclude_border": self.exclude_border, "overlap": self.overlap, "num_sigma": self.num_sigma } if self.detector_method is not blob_doh: spot_finding_args["exclude_border"] = self.exclude_border fitted_blobs_array: np.ndarray = self.detector_method( data_image, **spot_finding_args) if fitted_blobs_array.shape[0] == 0: empty_spot_attrs = SpotAttributes.empty( extra_fields=[Features.INTENSITY, Features.SPOT_ID]) return PerImageSliceSpotResults(spot_attrs=empty_spot_attrs, extras=None) # measure intensities z_inds = fitted_blobs_array[:, 0].astype(int) y_inds = fitted_blobs_array[:, 1].astype(int) x_inds = fitted_blobs_array[:, 2].astype(int) radius = np.round(fitted_blobs_array[:, 3] * np.sqrt(3)) data_image = np.asarray(data_image) intensities = data_image[tuple([z_inds, y_inds, x_inds])] # construct dataframe spot_data = pd.DataFrame( data={ Features.INTENSITY: intensities, Axes.ZPLANE.value: z_inds, Axes.Y.value: y_inds, Axes.X.value: x_inds, Features.SPOT_RADIUS: radius, }) spots = SpotAttributes(spot_data) spots.data[Features.SPOT_ID] = np.arange(spots.data.shape[0]) return PerImageSliceSpotResults(spot_attrs=spots, extras=None)
def _create_spot_attributes( self, region_properties: List[_RegionProperties], decoded_image: np.ndarray, target_map: TargetsMap, n_processes: Optional[int] = None ) -> Tuple[SpotAttributes, np.ndarray]: """ Parameters ---------- region_properties : List[_RegionProperties] Properties of the each connected component. Output of skimage.measure.regionprops decoded_image : np.ndarray Image whose pixels correspond to the targets that the given position in the ImageStack decodes to. target_map : TargetsMap Unique mapping between string target names and int target IDs. n_processes : Optional[int]=None number of processes to devote to measuring spot properties. If None, defaults to the result of os.nproc() Returns ------- pd.DataFrame : DataFrame containing x, y, z, radius, and target name for each connected component feature. np.ndarray[bool] : An array with length equal to the number of features. If zero, indicates that a feature has failed area filters. """ with ThreadPoolExecutor(max_workers=n_processes) as tpe: mapfunc = tpe.map applyfunc = partial(self._single_spot_attributes, decoded_image=decoded_image, target_map=target_map, min_area=self._min_area, max_area=self._max_area) iterable = tqdm(region_properties, disable=(not StarfishConfig().verbose)) results = mapfunc(applyfunc, iterable) if not results: # no spots found warnings.warn( "No spots found, please adjust threshold parameters") return SpotAttributes.empty(extra_fields=['target']), np.array( 0, dtype=bool) spot_attrs, passes_area_filter = zip(*results) # update passes filter passes_filter = np.array(passes_area_filter, dtype=bool) spot_attributes = SpotAttributes( pd.DataFrame.from_records(spot_attrs)) spot_attributes.data[Features.SPOT_ID] = np.arange( 0, len(spot_attributes.data)) return spot_attributes, passes_filter
def image_to_spots( self, data_image: Union[np.ndarray, xr.DataArray]) -> SpotAttributes: """ Find spots using a gaussian blob finding algorithm Parameters ---------- data_image : Union[np.ndarray, xr.DataArray] image containing spots to be detected Returns ------- SpotAttributes : DataFrame of metadata containing the coordinates, intensity and radius of each spot """ fitted_blobs_array: np.ndarray = self.detector_method( data_image, min_sigma=self.min_sigma, max_sigma=self.max_sigma, threshold=self.threshold, exclude_border=self.exclude_border, overlap=self.overlap, num_sigma=self.num_sigma) if fitted_blobs_array.shape[0] == 0: return SpotAttributes.empty( extra_fields=[Features.INTENSITY, Features.SPOT_ID]) # measure intensities z_inds = fitted_blobs_array[:, 0].astype(int) y_inds = fitted_blobs_array[:, 1].astype(int) x_inds = fitted_blobs_array[:, 2].astype(int) radius = np.round(fitted_blobs_array[:, 3] * np.sqrt(3)) data_image = data_image.values if isinstance( data_image, xr.DataArray) else data_image intensities = data_image[tuple([z_inds, y_inds, x_inds])] # construct dataframe spot_data = pd.DataFrame( data={ Features.INTENSITY: intensities, Axes.ZPLANE.value: z_inds, Axes.Y.value: y_inds, Axes.X.value: x_inds, Features.SPOT_RADIUS: radius, }) spots = SpotAttributes(spot_data) spots.data[Features.SPOT_ID] = np.arange(spots.data.shape[0]) return spots
def build_traces_sequential(spot_results: SpotFindingResults, **kwargs) -> IntensityTable: """ Build spot traces without merging across channels and imaging rounds. Used for sequential methods like smFIsh. Parameters ---------- spot_results: SpotFindingResults Spots found across rounds/channels of an ImageStack Returns ------- IntensityTable : concatenated input SpotAttributes, converted to an IntensityTable object """ all_spots = pd.concat([sa.data for sa in spot_results.values()], sort=True) intensity_table = IntensityTable.zeros( spot_attributes=SpotAttributes(all_spots), ch_labels=spot_results.ch_labels, round_labels=spot_results.round_labels, ) i = 0 for (r, c), attrs in spot_results.items(): for _, row in attrs.data.iterrows(): selector = dict(features=i, c=c, r=r) intensity_table.loc[selector] = row[Features.INTENSITY] i += 1 return intensity_table
def test_intensity_table_can_be_created_from_spot_attributes(): """ This test creates an IntensityTable from spot attributes, and verifies that the size matches what was requested and that the values are all zero. """ # input has two spots spot_attributes = SpotAttributes( pd.DataFrame( data=np.array( [[1, 1, 1, 1], [2, 2, 2, 1]] ), columns=[Axes.ZPLANE, Axes.Y, Axes.X, Features.SPOT_RADIUS] ) ) intensities = IntensityTable.zeros( spot_attributes, ch_labels=np.arange(1), round_labels=np.arange(3) ) assert intensities.sizes[Axes.CH] == 1 assert intensities.sizes[Axes.ROUND] == 3 assert intensities.sizes[Features.AXIS] == 2 assert np.all(intensities.values == 0)
def measure_spot_intensity( image: Union[np.ndarray, xr.DataArray], spots: SpotAttributes, measurement_function: Callable[[Sequence], Number], radius_is_gyration: bool = False, ) -> pd.Series: """measure the intensity of each spot in spots in the corresponding image Parameters ---------- image : Union[np.ndarray, xr.DataArray], 3-d volume in which to measure intensities spots : pd.DataFrame SpotAttributes table containing coordinates and radii of spots measurement_function : Callable[[Sequence], Number]) Function to apply over the spot volumes to identify the intensity (e.g. max, mean, ...) 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) Returns ------- pd.Series : Intensities for each spot in SpotAttributes """ def fn(row: pd.Series) -> Number: data = image[row['z_min']:row['z_max'], row['y_min']:row['y_max'], row['x_min']:row['x_max']] return measurement_function(data) if radius_is_gyration: radius = np.ceil( spots.data[Features.SPOT_RADIUS]).astype(int) + 1 # round up else: radius = spots.data[Features.SPOT_RADIUS].astype( int) # truncate down to nearest integer for v, max_size in zip(['z', 'y', 'x'], image.shape): # numpy does exclusive max indexing, so need to subtract 1 from min to get centered box spots.data[f'{v}_min'] = np.clip(spots.data[v] - (radius - 1), 0, None) spots.data[f'{v}_max'] = np.clip(spots.data[v] + radius, None, max_size) return spots.data[['z_min', 'z_max', 'y_min', 'y_max', 'x_min', 'x_max']].astype(int).apply(fn, axis=1)
def spot_attribute_factory(n: int) -> SpotAttributes: """ Construct SpotAttributes with n synthetic attributes. Each attribute has radius 1 and x, y, z coordinates equal to their index i in [0, n) """ return SpotAttributes( pd.DataFrame( data=np.array([[i, i, i, 1] for i in np.arange(n)]), columns=[Axes.ZPLANE, Axes.Y, Axes.X, Features.SPOT_RADIUS]))
def image_to_spots( self, data_image: np.ndarray, ) -> PerImageSliceSpotResults: """ Parameters ---------- data_image : np.ndarray three-dimensional image containing spots to be detected Returns ------- PerImageSpotResults : includes a SpotAttributes DataFrame of metadata containing the coordinates, intensity and radius of each spot, as well as any extra information collected during spot finding. """ data_image = np.asarray(data_image) with warnings.catch_warnings(): warnings.simplefilter( 'ignore', FutureWarning) # trackpy numpy indexing warning warnings.simplefilter('ignore', UserWarning) # yielded if black images attributes = locate( data_image, diameter=self.diameter, minmass=self.minmass, maxsize=self.maxsize, separation=self.separation, noise_size=self.noise_size, smoothing_size=self.smoothing_size, threshold=self.threshold, percentile=self.percentile, preprocess=self.preprocess, max_iterations=self.max_iterations, ) # when zero spots are detected, 'ep' is missing from the trackpy locate results. if attributes.shape[0] == 0: attributes['ep'] = [] # TODO ambrosejcarr: data should always be at least pseudo-3d, this may not be necessary # TODO ambrosejcarr: this is where max vs. sum vs. mean would be parametrized. # here, total_intensity = sum, intensity = max new_colnames = [ 'y', 'x', 'total_intensity', 'radius', 'eccentricity', 'intensity', 'raw_mass', 'ep' ] if len(data_image.shape) == 3: attributes.columns = ['z'] + new_colnames else: attributes.columns = new_colnames attributes['spot_id'] = np.arange(attributes.shape[0]) return PerImageSliceSpotResults(spot_attrs=SpotAttributes(attributes), extras=None)
def image_to_spots(self, data_image: Union[np.ndarray, xr.DataArray]) -> SpotAttributes: """ Find spots using a gaussian blob finding algorithm Parameters ---------- data_image : Union[np.ndarray, xr.DataArray] image containing spots to be detected Returns ------- SpotAttributes : DataFrame of metadata containing the coordinates, intensity and radius of each spot """ fitted_blobs_array: np.ndarray = self.detector_method( data_image, self.min_sigma, self.max_sigma, self.num_sigma, self.threshold, self.overlap ) if fitted_blobs_array.shape[0] == 0: return SpotAttributes.empty(extra_fields=['intensity', 'spot_id']) # create the SpotAttributes Table columns = [Axes.ZPLANE.value, Axes.Y.value, Axes.X.value, Features.SPOT_RADIUS] fitted_blobs = pd.DataFrame(data=fitted_blobs_array, columns=columns) # convert standard deviation of gaussian kernel used to identify spot to radius of spot converted_radius = np.round(fitted_blobs[Features.SPOT_RADIUS] * np.sqrt(3)) fitted_blobs[Features.SPOT_RADIUS] = converted_radius # convert the array to int so it can be used to index spots = SpotAttributes(fitted_blobs) spots.data['intensity'] = measure_spot_intensity( data_image, spots, self.measurement_function) spots.data['spot_id'] = np.arange(spots.data.shape[0]) return spots
def image_to_spots( self, data_image: Union[np.ndarray, xr.DataArray]) -> SpotAttributes: """ Parameters ---------- data_image : np.ndarray three-dimensional image containing spots to be detected Returns ------- SpotAttributes : spot attributes table for all detected spots """ data_image = np.asarray(data_image) with warnings.catch_warnings(): warnings.simplefilter( 'ignore', FutureWarning) # trackpy numpy indexing warning warnings.simplefilter('ignore', UserWarning) # yielded if black images attributes = locate(data_image, diameter=self.diameter, minmass=self.minmass, maxsize=self.maxsize, separation=self.separation, noise_size=self.noise_size, smoothing_size=self.smoothing_size, threshold=self.threshold, percentile=self.percentile, preprocess=self.preprocess) # when zero spots are detected, 'ep' is missing from the trackpy locate results. if attributes.shape[0] == 0: attributes['ep'] = [] # TODO ambrosejcarr: data should always be at least pseudo-3d, this may not be necessary # TODO ambrosejcarr: this is where max vs. sum vs. mean would be parametrized. # here, total_intensity = sum, intensity = max new_colnames = [ 'y', 'x', 'total_intensity', 'radius', 'eccentricity', 'intensity', 'raw_mass', 'ep' ] if len(data_image.shape) == 3: attributes.columns = ['z'] + new_colnames else: attributes.columns = new_colnames attributes['spot_id'] = np.arange(attributes.shape[0]) # convert these to int so it can be used to index attributes.x = attributes.x.astype(int) attributes.y = attributes.y.astype(int) attributes.z = attributes.z.astype(int) return SpotAttributes(attributes)
def measure_intensities_at_spot_locations_across_imagestack( data_image: ImageStack, reference_spots: PerImageSliceSpotResults, measurement_function: Callable[[np.ndarray], Number], radius_is_gyration: bool = False) -> SpotFindingResults: """given spots found from a reference image, find those spots across a data_image Parameters ---------- data_image : ImageStack ImageStack containing multiple volumes for which spots' intensities must be calculated reference_spots : PerImageSliceSpotResults Spots found in a reference image measurement_function : Callable[[np.ndarray], Number]) Function to apply over the spot volumes to identify the intensity (e.g. max, mean, ...) 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) Returns ------- SpotFindingResults : A Dict of tile indices and their corresponding measured SpotAttributes """ ch_labels = data_image.axis_labels(Axes.CH) round_labels = data_image.axis_labels(Axes.ROUND) spot_results = SpotFindingResults( imagestack_coords=data_image.xarray.coords, log=data_image.log) # measure spots in each tile indices = product(ch_labels, round_labels) for c, r in indices: tile_indices = {Axes.ROUND: r, Axes.CH: c} if reference_spots.spot_attrs.data.empty: # if no spots found don't measure spot_results[tile_indices] = reference_spots else: image, _ = data_image.get_slice({Axes.CH: c, Axes.ROUND: r}) blob_intensities: pd.Series = measure_intensities_at_spot_locations_in_image( image, reference_spots.spot_attrs, measurement_function, radius_is_gyration=radius_is_gyration) # copy reference spot positions and attributes tile_spots = SpotAttributes(reference_spots.spot_attrs.data.copy()) # fill in intensities tile_spots.data[Features.INTENSITY] = blob_intensities spot_results[tile_indices] = PerImageSliceSpotResults( spot_attrs=tile_spots, extras=None) return spot_results
def combine_spot_attributes_by_round_channel( spot_attributes_lists: Sequence[Tuple[PerImageSliceSpotResults, Mapping[Axes, int]]], ) -> List[Tuple[PerImageSliceSpotResults, Mapping[Axes, int]]]: # first bin by indices bins: MutableMapping[Tuple[int, int], List[SpotAttributes]] = collections.defaultdict(list) for spot_attributes_list, indices in spot_attributes_lists: bins[(indices[Axes.ROUND], indices[Axes.CH])].append(spot_attributes_list.spot_attrs) return [ ( PerImageSliceSpotResults(SpotAttributes.combine(spot_attributes_lists), None), {Axes.ROUND: indices[0], Axes.CH: indices[1]}, ) for indices, spot_attributes_lists in bins.items() ]
def intensity_table_factory() -> IntensityTable: """IntensityTable with a single feature that was measured over 2 channels and 2 rounds.""" intensities = np.array([[[0, 3], [4, 0]]], dtype=float) spot_attribute_data = pd.DataFrame( data=[0, 0, 0, 1], index=[Axes.ZPLANE, Axes.Y, Axes.X, Features.SPOT_RADIUS]).T spot_attributes = SpotAttributes(spot_attribute_data) intensity_table = IntensityTable.from_spot_data( intensities, spot_attributes, ch_values=np.arange(intensities.shape[1]), round_values=np.arange(intensities.shape[2]), ) return intensity_table
def intensity_table_factory(data: np.ndarray = np.array([[[0, 3], [4, 0]] ])) -> IntensityTable: """IntensityTable with a single feature that was measured over 2 channels and 2 rounds.""" # generates spot attributes equal in size to the number of passed features. # each attribute has coordinates (z, y, x) equal to the feature index, and radius 1. spot_attributes_data = pd.DataFrame( data=np.array([[i, i, i, 1] for i in np.arange(data.shape[0])]), columns=[Axes.ZPLANE, Axes.Y, Axes.X, Features.SPOT_RADIUS]) spot_attributes = SpotAttributes(spot_attributes_data) intensity_table = IntensityTable.from_spot_data( data, spot_attributes, ch_values=np.arange(data.shape[1]), round_values=np.arange(data.shape[2]), ) return intensity_table
def filterSpots(spots, mask, oneIndex=False, invert=False): # takes a SpotFindingResults, ImageStack, and BinaryMaskCollection # to return a set of SpotFindingResults that are masked by the binary mask spot_attributes_list = [] maskMat = mask.to_label_image().xarray.values maskMat[maskMat > 1] = 1 if invert: maskMat = 1 - maskMat maskSize = np.shape(maskMat) for item in spots.items(): selectedSpots = item[1].spot_attrs.data selectedSpots = selectedSpots.reset_index(drop=True) selRow = [] for ind, row in selectedSpots.iterrows(): if maskMat[int(row["y"]) - oneIndex][int(row["x"]) - oneIndex] == 1: selRow.append(ind) selectedSpots = selectedSpots.iloc[selRow] selectedSpots = selectedSpots.drop_duplicates( ) # unsure why the query necessitates this spotResults = PerImageSliceSpotResults( spot_attrs=SpotAttributes(selectedSpots), extras=None) spot_attributes_list.append((spotResults, { Axes.ROUND: item[0][0], Axes.CH: item[0][1] })) newCoords = {} renameAxes = { "x": Coordinates.X.value, "y": Coordinates.Y.value, "z": Coordinates.Z.value } for k in renameAxes.keys(): newCoords[renameAxes[k]] = spots.physical_coord_ranges[k] newSpots = SpotFindingResults(imagestack_coords=newCoords, log=starfish.Log(), spot_attributes_list=spot_attributes_list) return newSpots
def concatenate_spot_attributes_to_intensities( spot_attributes: Sequence[Tuple[SpotAttributes, Dict[Axes, int]]] ) -> IntensityTable: """ Merge multiple spot attributes frames into a single IntensityTable without merging across channels and imaging rounds Parameters ---------- spot_attributes : Sequence[Tuple[SpotAttributes, Dict[Axes, int]]] A sequence of SpotAttribute objects and the indices (channel, round) that each object is associated with. Returns ------- IntensityTable : concatenated input SpotAttributes, converted to an IntensityTable object """ ch_values: Sequence[int] = sorted( set(inds[Axes.CH] for _, inds in spot_attributes)) round_values: Sequence[int] = sorted( set(inds[Axes.ROUND] for _, inds in spot_attributes)) all_spots = pd.concat([sa.data for sa, inds in spot_attributes], sort=True) # this drop call ensures only x, y, z, radius, and quality, are passed to the IntensityTable features_coordinates = all_spots.drop(['spot_id', 'intensity'], axis=1) intensity_table = IntensityTable.zeros( SpotAttributes(features_coordinates), ch_values, round_values, ) i = 0 for attrs, inds in spot_attributes: for _, row in attrs.data.iterrows(): selector = dict(features=i, c=inds[Axes.CH], r=inds[Axes.ROUND]) intensity_table.loc[selector] = row['intensity'] i += 1 return intensity_table
def from_image_stack(cls, image_stack, crop_x: int = 0, crop_y: int = 0, crop_z: int = 0) -> "IntensityTable": """Generate an IntensityTable from all the pixels in the ImageStack Parameters ---------- crop_x : int Number of pixels to crop from both top and bottom of x. crop_y : int Number of pixels to crop from both top and bottom of y. crop_z : int Number of pixels to crop from both top and bottom of z. image_stack : ImageStack ImageStack containing pixels to be treated as intensities. Returns ------- IntensityTable : IntensityTable containing one intensity per pixel (across channels and rounds). """ # verify the image is large enough to crop assert crop_z * 2 < image_stack.shape['z'] assert crop_y * 2 < image_stack.shape['y'] assert crop_x * 2 < image_stack.shape['x'] zmin = image_stack.axis_labels(Axes.ZPLANE)[crop_z] ymin = crop_y xmin = crop_x zmax = image_stack.axis_labels(Axes.ZPLANE)[-crop_z - 1] ymax = image_stack.shape['y'] - crop_y xmax = image_stack.shape['x'] - crop_x cropped_stack = image_stack.sel({ Axes.ZPLANE: (zmin, zmax), Axes.Y: (ymin, ymax), Axes.X: (xmin, xmax) }) data = cropped_stack.xarray.transpose( Axes.ZPLANE.value, Axes.Y.value, Axes.X.value, Axes.CH.value, Axes.ROUND.value, ) # (pixels, ch, round) intensity_data = data.values.reshape(-1, image_stack.num_chs, image_stack.num_rounds) # IntensityTable pixel coordinates z = image_stack.axis_labels(Axes.ZPLANE) y = np.arange(ymin, ymax) x = np.arange(xmin, xmax) feature_attribute_data = pd.DataFrame(data=np.array( list(product(z, y, x))), columns=['z', 'y', 'x']) feature_attribute_data[Features.SPOT_RADIUS] = np.full( feature_attribute_data.shape[0], fill_value=0.5) pixel_coordinates = SpotAttributes(feature_attribute_data) return IntensityTable.from_spot_data( intensity_data, pixel_coordinates, image_stack.axis_labels(Axes.CH), image_stack.axis_labels(Axes.ROUND), )
def synthetic_intensities(cls, codebook, num_z: int = 12, height: int = 50, width: int = 40, n_spots=10, mean_fluor_per_spot=200, mean_photons_per_fluor=50) -> "IntensityTable": """ Creates an IntensityTable with synthetic spots, that correspond to valid codes in a provided codebook. Parameters ---------- codebook : Codebook Starfish codebook object. num_z : int Number of z-planes to use when localizing spots. height : int y dimension of each synthetic plane. width : int x dimension of each synthetic plane. n_spots : int Number of spots to generate. mean_fluor_per_spot : int Mean number of fluorophores per spot. mean_photons_per_fluor : int Mean number of photons per fluorophore. Returns ------- IntensityTable """ # TODO nsofroniew: right now there is no jitter on x-y positions of the spots z = np.random.randint(0, num_z, size=n_spots) y = np.random.randint(0, height, size=n_spots) x = np.random.randint(0, width, size=n_spots) r = np.empty(n_spots) r.fill( np.nan) # radius is a function of the point-spread gaussian size spot_attributes = SpotAttributes( pd.DataFrame({ Axes.ZPLANE.value: z, Axes.Y.value: y, Axes.X.value: x, Features.SPOT_RADIUS: r })) # empty data tensor data = np.zeros(shape=(n_spots, *codebook.shape[1:])) targets = np.random.choice(codebook.coords[Features.TARGET], size=n_spots, replace=True) expected_bright_locations = np.where(codebook.loc[targets]) # create a binary matrix where "on" spots are 1 data[expected_bright_locations] = 1 # add physical properties of fluorescence data *= np.random.poisson(mean_photons_per_fluor, size=data.shape) data *= np.random.poisson(mean_fluor_per_spot, size=data.shape) # convert data to float for consistency with starfish data = preserve_float_range(data) assert 0 < data.max() <= 1 intensities = cls.from_spot_data(data, spot_attributes, np.arange(data.shape[1]), np.arange(data.shape[2])) intensities[Features.TARGET] = (Features.AXIS, targets) return intensities
def image_to_spots(self, data_image: np.ndarray) -> PerImageSliceSpotResults: """measure attributes of spots detected by binarizing the image using the selected threshold Parameters ---------- data_image : np.ndarray image containing spots to be detected Returns ------- PerImageSpotResults : includes a SpotAttributes DataFrame of metadata containing the coordinates, intensity and radius of each spot, as well as any extra information collected during spot finding. """ optimal_threshold, thresholds, spot_counts = self._compute_threshold( data_image) data_image = np.asarray(data_image) # identify each spot's size by binarizing and calculating regionprops masked_image = data_image[:, :] > optimal_threshold labels = label(masked_image)[0] spot_props = regionprops(np.squeeze(labels)) # mask spots whose areas are too small or too large for spot_prop in spot_props: if spot_prop.area < self.min_obj_area or spot_prop.area > self.max_obj_area: masked_image[0, spot_prop.coords[:, 0], spot_prop.coords[:, 1]] = 0 # store re-calculated regionprops and labels based on the area-masked image labels = label(masked_image)[0] if self.verbose: print('computing final spots ...') spot_coords = peak_local_max(data_image, min_distance=self.min_distance, threshold_abs=optimal_threshold, exclude_border=False, indices=True, num_peaks=np.inf, footprint=None, labels=labels) res = { Axes.X.value: spot_coords[:, 2], Axes.Y.value: spot_coords[:, 1], Axes.ZPLANE.value: spot_coords[:, 0], Features.SPOT_RADIUS: 1, Features.SPOT_ID: np.arange(spot_coords.shape[0]), Features.INTENSITY: data_image[spot_coords[:, 0], spot_coords[:, 1], spot_coords[:, 2]], } extras: Mapping[str, Any] = { "threshold": optimal_threshold, "thresholds": thresholds, "spot_counts": spot_counts } return PerImageSliceSpotResults(spot_attrs=SpotAttributes( pd.DataFrame(res)), extras=extras)
def image_to_spots( self, data_image: Union[np.ndarray, xr.DataArray]) -> SpotAttributes: """measure attributes of spots detected by binarizing the image using the selected threshold Parameters ---------- data_image : Union[np.ndarray, xr.DataArray] image containing spots to be detected Returns ------- SpotAttributes Attributes for each detected spot """ threshold = self._compute_threshold(data_image) data_image = np.asarray(data_image) # identify each spot's size by binarizing and calculating regionprops masked_image = data_image[:, :] > threshold labels = label(masked_image)[0] spot_props = regionprops(np.squeeze(labels)) # mask spots whose areas are too small or too large for spot_prop in spot_props: if spot_prop.area < self.min_obj_area or spot_prop.area > self.max_obj_area: masked_image[0, spot_prop.coords[:, 0], spot_prop.coords[:, 1]] = 0 # store re-calculated regionprops and labels based on the area-masked image labels = label(masked_image)[0] if self.verbose: print('computing final spots ...') spot_coords = peak_local_max(data_image, min_distance=self.min_distance, threshold_abs=threshold, exclude_border=False, indices=True, num_peaks=np.inf, footprint=None, labels=labels) # TODO how to get the radius? unlikely that this can be pulled out of # self._spot_props, since the last call to peak_local_max can find multiple # peaks per label res = { Axes.X.value: spot_coords[:, 2], Axes.Y.value: spot_coords[:, 1], Axes.ZPLANE.value: spot_coords[:, 0], Features.SPOT_RADIUS: 1, Features.SPOT_ID: np.arange(spot_coords.shape[0]), Features.INTENSITY: data_image[spot_coords[:, 0], spot_coords[:, 1], spot_coords[:, 2]] } return SpotAttributes(pd.DataFrame(res))