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 build_spot_traces_exact_match(spot_results: SpotFindingResults, **kwargs) -> IntensityTable: """ Combines spots found in matching x/y positions across rounds and channels of an ImageStack into traces represented as an IntensityTable. Parameters ----------- spot_results: SpotFindingResults Spots found across rounds/channels of an ImageStack """ # create IntensityTable with same x/y/z info accross all r/ch spot_attributes = list(spot_results.values())[0].spot_attrs intensity_table = IntensityTable.zeros( spot_attributes=spot_attributes, round_labels=spot_results.round_labels, ch_labels=spot_results.ch_labels, ) for r, c in spot_results.keys(): value = spot_results[{ Axes.ROUND: r, Axes.CH: c }].spot_attrs.data[Features.INTENSITY] # if no exact match set value to 0 value = 0 if value.empty else value intensity_table.loc[dict(c=c, r=r)] = value return intensity_table
def run(self, spots: SpotFindingResults, *args) -> DecodedIntensityTable: """ Decode spots by looking up the associated target value for the round and ch each spot is in. Parameters ---------- spots: SpotFindingResults A Dict of tile indices and their corresponding measured spots Returns ------- DecodedIntensityTable : IntensityTable decoded and appended with Features.TARGET and values. """ lookup_table: Dict[Tuple, str] = {} for target in self.codebook[Features.TARGET]: for ch_label in self.codebook[Axes.CH.value]: for round_label in self.codebook[Axes.ROUND.value]: if self.codebook.loc[target, round_label, ch_label]: lookup_table[(int(round_label), int(ch_label))] = str(target.values) for r_ch_index, results in spots.items(): target = lookup_table[ r_ch_index] if r_ch_index in lookup_table else 'nan' results.spot_attrs.data[Features.TARGET] = target intensities = build_traces_sequential(spots) return DecodedIntensityTable(intensities)
def _merge_spots_by_round( spot_results: SpotFindingResults) -> Dict[int, pd.DataFrame]: """Merge DataFrames containing spots from different channels into one DataFrame per round. Parameters ---------- spot_results : Dict[Tuple[int, int], pd.DataFrame] Output of _find_spots. Dictionary mapping (round, channel) volumes to the spots detected in them. Returns ------- Dict[int, pd.DataFrame] Dictionary mapping round volumes to the spots detected in them. Contains an additional column labeled by Axes.CH which identifies the channel in which a given spot was detected. """ # add channel information to each table and add it to round_data round_data: Mapping[int, List] = defaultdict(list) for (r, c), df in spot_results.items(): df = df.spot_attrs.data df[Axes.CH.value] = np.full(df.shape[0], c) round_data[r].append(df) # create one dataframe per round round_dataframes = { k: pd.concat(v, axis=0).reset_index().drop('index', axis=1) for k, v in round_data.items() } return round_dataframes
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 run( self, image_stack: ImageStack, reference_image: Optional[ImageStack] = None, n_processes: Optional[int] = None, *args, **kwargs ) -> SpotFindingResults: """ Find spots in the given ImageStack using a local maxima finding algorithm. If a reference image is provided the spots will be detected there then measured across all rounds and channels in the corresponding ImageStack. If a reference_image is not provided 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. Parameters ---------- image_stack : ImageStack ImageStack where we find the spots in. reference_image : Optional[ImageStack] (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. n_processes : Optional[int] = None, Number of processes to devote to spot finding. """ spot_finding_method = partial(self.image_to_spots, **self.kwargs) if reference_image: shape = reference_image.shape assert shape[Axes.ROUND] == 1 assert shape[Axes.CH] == 1 spot_attributes_lists = reference_image.transform( func=spot_finding_method, group_by=determine_axes_to_group_by(self.is_volume), n_processes=n_processes ) spot_attributes_lists = combine_spot_attributes_by_round_channel(spot_attributes_lists) assert len(spot_attributes_lists) == 1 results = spot_finding_utils.measure_intensities_at_spot_locations_across_imagestack( data_image=image_stack, reference_spots=spot_attributes_lists[0][0], measurement_function=self.measurement_function) else: spot_attributes_lists = image_stack.transform( func=spot_finding_method, group_by=determine_axes_to_group_by(self.is_volume), n_processes=n_processes ) spot_attributes_lists = combine_spot_attributes_by_round_channel(spot_attributes_lists) results = SpotFindingResults(imagestack_coords=image_stack.xarray.coords, log=image_stack.log, spot_attributes_list=spot_attributes_lists) return results
def run( self, image_stack: ImageStack, reference_image: Optional[ImageStack] = None, n_processes: Optional[int] = None, *args, ) -> SpotFindingResults: """ Find spots in the given ImageStack using a gaussian blob finding algorithm. If a reference image is provided the spots will be detected there then measured across all rounds and channels in the corresponding ImageStack. If a reference_image is not provided 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. Parameters ---------- image_stack : ImageStack ImageStack where we find the spots in. reference_image : Optional[ImageStack] (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. n_processes : Optional[int] = None, Number of processes to devote to spot finding. """ spot_finding_method = partial(self.image_to_spots, *args) if reference_image: data_image = reference_image._squeezed_numpy( *{Axes.ROUND, Axes.CH}) if self.detector_method is blob_doh and data_image.ndim > 2: raise ValueError("blob_doh only support 2d images") reference_spots = spot_finding_method(data_image) results = spot_finding_utils.measure_intensities_at_spot_locations_across_imagestack( data_image=image_stack, reference_spots=reference_spots, measurement_function=self.measurement_function) else: if self.detector_method is blob_doh and self.is_volume: raise ValueError("blob_doh only support 2d images") spot_attributes_list = image_stack.transform( func=spot_finding_method, group_by=determine_axes_to_group_by(self.is_volume), n_processes=n_processes) results = SpotFindingResults( imagestack_coords=image_stack.xarray.coords, log=image_stack.log, spot_attributes_list=spot_attributes_list) return results
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 run( self, image_stack: ImageStack, reference_image: Optional[ImageStack] = None, n_processes: Optional[int] = None, *args, ) -> SpotFindingResults: """ Find spots. Parameters ---------- image_stack : ImageStack ImageStack where we find the spots in. 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. n_processes : Optional[int] = None, Number of processes to devote to spot finding. """ spot_finding_method = partial(self.image_to_spots, *args) if reference_image: data_image = reference_image._squeezed_numpy( *{Axes.ROUND, Axes.CH}) reference_spots = spot_finding_method(data_image) results = spot_finding_utils.measure_intensities_at_spot_locations_across_imagestack( image_stack, reference_spots, measurement_function=self.measurement_function, radius_is_gyration=self.radius_is_gyration) else: spot_attributes_list = image_stack.transform( func=spot_finding_method, group_by=determine_axes_to_group_by(self.is_volume), n_processes=n_processes) results = SpotFindingResults( imagestack_coords=image_stack.xarray.coords, log=image_stack.log, spot_attributes_list=spot_attributes_list) return results
args.segmentation_loc ): # if this is true, going to assume baysorStaged dir-wise FOV structure segmentation = {} for name in transcripts.keys(): segmentation[name] = pd.read_csv("{}/{}/segmentation.csv".format( args.segmentation_loc, name)) spots = False if args.spots_pkl: spots = pickle.load(open(args.spots_pkl, "rb")) elif args.has_spots: # load spots from exp dir spots = {} for k in transcripts.keys(): spots[k] = SpotFindingResults.load( "{}/spots/{}_SpotFindingResults.json".format( args.exp_output, k)) size = [0, 0, 0] if args.x_size: # specify in CWL that all or none must be specified, only needed when doRipley size[0] = args.x_size size[1] = args.y_size size[2] = args.z_size fovs = False if args.exp_output: # reading in from experiment can have multiple FOVs fovs = [k for k in transcripts.keys()] run(transcripts, codebook, size, fovs, spots, roi, segmentation, args.run_ripley, args.save_pdf)