def measure_spot_intensities( data_image: ImageStack, spot_attributes: SpotAttributes, measurement_function: Callable[[Sequence], Number], radius_is_gyration: bool = False, ) -> IntensityTable: """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 spot_attributes : pd.Dataframe Locations 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 ------- IntensityTable : 3d tensor of (spot, channel, round) information for each coded spot """ # determine the shape of the intensity table n_ch = data_image.shape[Axes.CH] n_round = data_image.shape[Axes.ROUND] # construct the empty intensity table intensity_table = IntensityTable.empty_intensity_table( spot_attributes=spot_attributes, n_ch=n_ch, n_round=n_round, ) # if no spots were detected, return the empty IntensityTable if intensity_table.sizes[Features.AXIS] == 0: return intensity_table # fill the intensity table indices = product(range(n_ch), range(n_round)) for c, r in indices: image, _ = data_image.get_slice({Axes.CH: c, Axes.ROUND: r}) blob_intensities: pd.Series = measure_spot_intensity( image, spot_attributes, measurement_function, radius_is_gyration=radius_is_gyration) intensity_table[:, c, r] = blob_intensities return intensity_table
def verify_stack_data( stack: ImageStack, selectors: Mapping[Axes, Union[int, slice]], expected_data: np.ndarray, ) -> Tuple[np.ndarray, Sequence[Axes]]: """Given an imagestack and a set of selectors, verify that the data referred to by the selectors matches the expected data. """ tile_data, axes = stack.get_slice(selectors) assert np.array_equal(tile_data, expected_data) return tile_data, axes
def run(self, image: ImageStack, in_place: bool = False) -> Optional[ImageStack]: """Register an ImageStack against a reference image. Parameters ---------- image : ImageStack The stack to be registered in_place : bool If false, return a new registered stack. Else, register in-place (default False) Returns ------- """ if not in_place: image = deepcopy(image) # TODO: (ambrosejcarr) is this the appropriate way of dealing with Z in registration? mp = image.max_proj(Axes.CH, Axes.ZPLANE) mp_numpy = mp._squeezed_numpy(Axes.CH, Axes.ZPLANE) reference_image_mp = self.reference_stack.max_proj( Axes.ROUND, Axes.CH, Axes.ZPLANE) reference_image_numpy = reference_image_mp._squeezed_numpy( Axes.ROUND, Axes.CH, Axes.ZPLANE) for r in image.axis_labels(Axes.ROUND): # compute shift between maximum projection (across channels) and dots, for each round # TODO: make the max projection array ignorant of axes ordering. shift, error = compute_shift(mp_numpy[r, :, :], reference_image_numpy, self.upsampling) print(f"For round: {r}, Shift: {shift}, Error: {error}") for c in image.axis_labels(Axes.CH): for z in image.axis_labels(Axes.ZPLANE): # apply shift to all zplanes, channels, and imaging rounds selector = {Axes.ROUND: r, Axes.CH: c, Axes.ZPLANE: z} data, axes = image.get_slice(selector=selector) assert len(axes) == 0 result = shift_im(data, shift) result = preserve_float_range(result) image.set_slice(selector=selector, data=result) if not in_place: return image return None
def verify_stack_fill( stack: ImageStack, selectors: Mapping[Axes, Union[int, slice]], expected_fill_value: Number, ) -> Tuple[np.ndarray, Sequence[Axes]]: """Given an imagestack and a set of selectors, verify that the data referred to by the selectors matches an expected fill value. """ tile_data, axes = stack.get_slice(selectors) expected_data = np.full(tile_data.shape, expected_fill_value, np.float32) assert np.array_equal(tile_data, expected_data) return tile_data, axes
def run(self, stack: ImageStack, transforms_list: TransformsList, in_place: bool = False, verbose: bool = False, *args, **kwargs) -> ImageStack: """Applies a list of transformations to an ImageStack Parameters ---------- stack : ImageStack Stack to be transformed. transforms_list: TransformsList The list of transform objects to apply to the ImageStack. in_place : bool if True, process ImageStack in-place, otherwise return a new stack verbose : bool if True, report on transformation progress (default = False) Returns ------- ImageStack : If in-place is False, return the results of the transforms as a new stack. Otherwise return the original stack. """ if not in_place: # create a copy of the ImageStack, call apply on that stack with in_place=True image_stack = deepcopy(stack) return self.run(image_stack, transforms_list, in_place=True, **kwargs) if verbose and StarfishConfig().verbose: transforms_list.transforms = tqdm(transforms_list.transforms) all_axes = {Axes.ROUND, Axes.CH, Axes.ZPLANE} for selector, _, transformation_object in transforms_list.transforms: other_axes = all_axes - set(selector.keys()) # iterate through remaining axes for axes in stack._iter_axes(other_axes): # combine all axes data to select one tile selector.update(axes) # type: ignore selected_image, _ = stack.get_slice(selector) warped_image = warp(selected_image, transformation_object, **kwargs).astype(np.float32) stack.set_slice(selector, warped_image) return stack