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)
Beispiel #2
0
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
Beispiel #3
0
    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)
Beispiel #5
0
    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
Beispiel #6
0
    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
Beispiel #7
0
    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
Beispiel #8
0
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)
Beispiel #9
0
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)
Beispiel #10
0
                                        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
Beispiel #11
0
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