def pixel_intensities_to_imagestack( intensities: IntensityTable, image_shape: Tuple[int, int, int]) -> ImageStack: """Re-create the pixel intensities from an IntensityTable Parameters ---------- intensities : IntensityTable intensities to transform into an ImageStack image_shape : Tuple[int, int, int] the dimensions of z, y, and x for the original image that the intensity table was generated from Returns ------- ImageStack : ImageStack containing Intensity information """ # reverses the process used to produce the intensity table in to_pixel_intensities data = intensities.values.reshape([ *image_shape, intensities.sizes[Axes.CH], intensities.sizes[Axes.ROUND] ]) data = data.transpose(4, 3, 0, 1, 2) return ImageStack.from_numpy(data)
def test_match_histograms(): linear_gradient = np.linspace(0, 0.5, 2000, dtype=np.float32) image = linear_gradient.reshape(2, 4, 5, 5, 10) # linear_gradient = np.linspace(0, 1, 10)[::-1] # grad = np.repeat(linear_gradient[np.newaxis, :], 10, axis=0) # image2 = np.tile(grad, (1, 2, 2, 10, 10)) # because of how the image was structured, every volume should be the same after # quantile normalization stack = ImageStack.from_numpy(image) mh = MatchHistograms({Axes.CH, Axes.ROUND}) results = mh.run(stack) assert len(np.unique(results.xarray.sum(("x", "y", "z")))) == 1 # because here we are allowing variation to persist across rounds, each # round within each channel should be different mh = MatchHistograms({Axes.CH}) results2 = mh.run(stack) assert len(np.unique(results2.xarray.sum(("x", "y", "z")))) == 2 # same as above, but verifying this functions for a different data shape (2 rounds, 4 channels) mh = MatchHistograms({Axes.ROUND}) results2 = mh.run(stack) assert len(np.unique(results2.xarray.sum(("x", "y", "z")))) == 4
def import_ilastik_probabilities( cls, path_to_h5_file: Union[str, Path], dataset_name: str = "exported_data") -> ImageStack: """ Import cell probabilities provided by ilastik as an ImageStack. Parameters ---------- path_to_h5_file : Union[str, Path] Path to the .h5 file outputted by ilastik dataset_name : str Name of dataset in ilastik Export Image Settings Returns ------- ImageStack : A new ImageStack created from the cell probabilities provided by ilastik. """ h5 = h5py.File(path_to_h5_file) probability_images = h5[dataset_name][:] h5.close() cell_probabilities, _ = probability_images[:, :, 0], probability_images[:, :, 1] label_array = ndi.label(cell_probabilities)[0] label_array = label_array[np.newaxis, np.newaxis, np.newaxis, ...] return ImageStack.from_numpy(label_array)
def test_reshaping_between_stack_and_intensities(): """ transform an pixels of an ImageStack into an IntensityTable and back again, then verify that the created Imagestack is the same as the original """ np.random.seed(777) image = ImageStack.from_numpy(np.random.rand(1, 2, 3, 4, 5).astype(np.float32)) pixel_intensities = IntensityTable.from_image_stack(image, 0, 0, 0) image_shape = (image.shape['z'], image.shape['y'], image.shape['x']) image_from_pixels = pixel_intensities_to_imagestack(pixel_intensities, image_shape) assert np.array_equal(image.xarray, image_from_pixels.xarray)
def run( self, stack: ImageStack, *args, ) -> ImageStack: """Performs the dimension reduction with the specifed function Parameters ---------- stack : ImageStack Stack to be filtered. Returns ------- ImageStack : Return the results of filter as a new stack. """ # Apply the reducing function reduced = stack.xarray.reduce(self.func.resolve(), dim=[dim.value for dim in self.dims], **self.kwargs) # Add the reduced dims back and align with the original stack reduced = reduced.expand_dims(tuple(dim.value for dim in self.dims)) reduced = reduced.transpose(*stack.xarray.dims) if self.level_method == Levels.CLIP: reduced = levels(reduced) elif self.level_method in (Levels.SCALE_BY_CHUNK, Levels.SCALE_BY_IMAGE): reduced = levels(reduced, rescale=True) elif self.level_method in (Levels.SCALE_SATURATED_BY_CHUNK, Levels.SCALE_SATURATED_BY_IMAGE): reduced = levels(reduced, rescale_saturated=True) # Update the physical coordinates physical_coords: MutableMapping[Coordinates, ArrayLike[Number]] = {} for axis, coord in ((Axes.X, Coordinates.X), (Axes.Y, Coordinates.Y), (Axes.ZPLANE, Coordinates.Z)): if axis in self.dims: # this axis was projected out of existence. assert coord.value not in reduced.coords physical_coords[coord] = [ np.average(stack._data.coords[coord.value]) ] else: physical_coords[coord] = reduced.coords[coord.value] reduced_stack = ImageStack.from_numpy(reduced.values, coordinates=physical_coords) return reduced_stack
def run( self, stack: ImageStack, *args, ) -> ImageStack: """Performs the dimension reduction with the specifed function Parameters ---------- stack : ImageStack Stack to be filtered. Returns ------- ImageStack : Return the results of filter as a new stack. """ # Apply the reducing function reduced = stack.xarray.reduce(self.func, dim=[dim.value for dim in self.dims], **self.kwargs) # Add the reduced dims back and align with the original stack reduced = reduced.expand_dims(tuple(dim.value for dim in self.dims)) reduced = reduced.transpose(*stack.xarray.dims) if self.clip_method == Clip.CLIP: reduced = preserve_float_range(reduced, rescale=False) else: reduced = preserve_float_range(reduced, rescale=True) # Update the physical coordinates physical_coords: MutableMapping[Coordinates, Sequence[Number]] = {} for axis, coord in ((Axes.X, Coordinates.X), (Axes.Y, Coordinates.Y), (Axes.ZPLANE, Coordinates.Z)): if axis in self.dims: # this axis was projected out of existence. assert coord.value not in reduced.coords physical_coords[coord] = [ np.average(stack._data.coords[coord.value]) ] else: physical_coords[coord] = cast(Sequence[Number], reduced.coords[coord.value]) reduced_stack = ImageStack.from_numpy(reduced.values, coordinates=physical_coords) return reduced_stack
def run( self, stack: ImageStack, verbose: bool = False, *args, ) -> Optional[ImageStack]: """Perform filtering of an image stack Parameters ---------- stack : ImageStack Stack to be filtered. in_place : bool if True, process ImageStack in-place, otherwise return a new stack verbose : bool if True, report on filtering progress (default = False) n_processes : Optional[int] Number of parallel processes to devote to calculating the filter Returns ------- ImageStack : The max projection of an image across one or more axis. """ max_projection = stack.xarray.max([dim.value for dim in self.dims]) max_projection = max_projection.expand_dims(tuple(dim.value for dim in self.dims)) max_projection = max_projection.transpose(*stack.xarray.dims) physical_coords: MutableMapping[Coordinates, Sequence[Number]] = {} for axis, coord in ( (Axes.X, Coordinates.X), (Axes.Y, Coordinates.Y), (Axes.ZPLANE, Coordinates.Z)): if axis in self.dims: # this axis was projected out of existence. assert coord.value not in max_projection.coords physical_coords[coord] = [np.average(stack.xarray.coords[coord.value])] else: physical_coords[coord] = max_projection.coords[coord.value] max_proj_stack = ImageStack.from_numpy(max_projection.values, coordinates=physical_coords) return max_proj_stack
def create_imagestack_from_codebook(pixel_dimensions: Tuple[int, int, int], spot_coordinates: Sequence[Tuple[int, int, int]], codebook: Codebook) -> ImageStack: """ creates a numpy array containing one spot per codebook entry at spot_coordinates. length of spot_coordinates must therefore match the number of codes in Codebook. """ assert len(spot_coordinates) == codebook.sizes[Features.TARGET] data_shape = (codebook.sizes[Axes.ROUND.value], codebook.sizes[Axes.CH.value], *pixel_dimensions) imagestack_data = np.zeros(data_shape, dtype=np.float32) for ((z, y, x), f) in zip(spot_coordinates, range(codebook.sizes[Features.TARGET])): imagestack_data[:, :, z, y, x] = codebook[f].transpose(Axes.ROUND.value, Axes.CH.value) # blur with a small non-isotropic kernel TODO make kernel smaller. imagestack_data = gaussian_filter(imagestack_data, sigma=(0, 0, 0.7, 1.5, 1.5)) return ImageStack.from_numpy(imagestack_data)
def random_data_image_stack_factory(): data = np.random.uniform(0, 1, 100).reshape(1, 1, 1, 10, 10).astype(np.float32) return ImageStack.from_numpy(data)
min_obj_area=args.big_peak_min, max_obj_area=args.big_peak_max, min_num_spots_detected=2500, is_volume=False, verbose=False) sd = Codebook.synthetic_one_hot_codebook(n_round=1, n_channel=1, n_codes=1) decoder = DecodeSpots.PerRoundMaxChannel(codebook=sd) block_dim = int(max(imarr.shape) * args.block_dim_fraction) SpotCoords = np.zeros((0, 2), dtype=np.int64) for i in range( 0, imarr.shape[-2] - 1, block_dim ): # subtracting 1 from range because starfish breaks with x or y axis size of 1 for j in range(0, imarr.shape[-1] - 1, block_dim): imgs = ImageStack.from_numpy(imarr[:, :, :, i:i + block_dim, j:j + block_dim]) imgs = bandpass.run(imgs).reduce({Axes.ZPLANE}, func="max") spots = lmp_small.run(imgs) decoded_intensities = decoder.run(spots=spots) spot_coords_small = np.stack([ decoded_intensities[Axes.Y.value], decoded_intensities[Axes.X.value] ]).T spots = lmp_big.run(imgs) decoded_intensities = decoder.run(spots=spots) spot_coords_big = np.stack([ decoded_intensities[Axes.Y.value], decoded_intensities[Axes.X.value] ]).T spot_coords = np.vstack([spot_coords_small, spot_coords_big]) spot_coords[:, 0] += i
def find_spots(input_path, output_path, intensity_percentile=99.995, filter_width=2, small_peak_min=4, small_peak_max=100, big_peak_min=25, big_peak_max=10000, small_peak_dist=2, big_peak_dist=0.75, block_dim_fraction=0.25, spot_pad_pixels=2, keep_existing=False): """ Find and keep only spots from stitched images. """ image_stack = imageio.volread(input_path) print(image_stack.shape) thresholded_image = np.copy(image_stack) _, height, width = image_stack.shape threshold = np.percentile(thresholded_image, intensity_percentile) thresholded_image[thresholded_image > threshold] = threshold + ( np.log(thresholded_image[thresholded_image > threshold] - threshold) / np.log(1.1)).astype(thresholded_image.dtype) #May need to fiddle with the sigma parameters in each step, depending on the image. #High Pass Filter (Background Subtraction) gaussian_high_pass = Filter.GaussianHighPass(sigma=(1, filter_width, filter_width), is_volume=True) # enhance brightness of spots laplace_filter = Filter.Laplace(sigma=(0.2, 0.5, 0.5), is_volume=True) local_max_peakfinder_small = FindSpots.LocalMaxPeakFinder( min_distance=small_peak_dist, stringency=0, min_obj_area=small_peak_min, max_obj_area=small_peak_max, min_num_spots_detected=2500, is_volume=True, verbose=True) local_max_peakfinder_big = FindSpots.LocalMaxPeakFinder( min_distance=big_peak_dist, stringency=0, min_obj_area=big_peak_min, max_obj_area=big_peak_max, min_num_spots_detected=2500, is_volume=True, verbose=True) synthetic_codebook = Codebook.synthetic_one_hot_codebook(n_round=1, n_channel=1, n_codes=1) decoder = DecodeSpots.PerRoundMaxChannel(codebook=synthetic_codebook) block_dimension = int(max(thresholded_image.shape) * block_dim_fraction) spot_coordinates = np.zeros((0, 2), dtype=np.int64) # Finding spots by block_dimension x block_dimension size blocks # We skip the blocks at the edges with the - 1 (TODO: pad to full block size) for row in range(0, height - 1, block_dimension): for column in range(0, width - 1, block_dimension): # Cutout block and expand dimensions for channel and round block = thresholded_image[np.newaxis, np.newaxis, :, row:row + block_dimension, column:column + block_dimension] images = ImageStack.from_numpy(block) high_pass_filtered = gaussian_high_pass.run(images, verbose=False, in_place=False) laplace = laplace_filter.run(high_pass_filtered, in_place=False, verbose=False) small_spots = local_max_peakfinder_small.run( laplace.reduce({Axes.ZPLANE}, func="max")) decoded_intensities = decoder.run(spots=small_spots) small_spot_coords = np.stack([ decoded_intensities[Axes.Y.value], decoded_intensities[Axes.X.value] ]).T big_spots = local_max_peakfinder_big.run( laplace.reduce({Axes.ZPLANE}, func="max")) decoded_intensities = decoder.run(spots=big_spots) big_spot_coords = np.stack([ decoded_intensities[Axes.Y.value], decoded_intensities[Axes.X.value] ]).T all_spot_coords = np.vstack([small_spot_coords, big_spot_coords]) all_spot_coords += (row, column) spot_coordinates = np.vstack([spot_coordinates, all_spot_coords]) # Copying over only non-zero pixels image_spots = np.zeros((height, width), dtype=np.uint16) for spot_coordinate in spot_coordinates: spot_column, spot_row = spot_coordinate for row in range(max(0, spot_column - spot_pad_pixels), min(spot_column + spot_pad_pixels + 1, height)): for column in range(max(0, spot_row - spot_pad_pixels), min(spot_row + spot_pad_pixels + 1, width)): # Max projecting over z-stack image_spots[row, column] = image_stack[:, row, column].max(0) imageio.imsave(output_path, image_spots) return image_spots