Пример #1
0
 def _in_place_apply(
     apply_func: Callable[..., Union[xr.DataArray, np.ndarray]], data: np.ndarray,
     clip_method: Union[str, Clip], **kwargs
 ) -> None:
     result = apply_func(data, **kwargs)
     if clip_method == Clip.CLIP:
         data[:] = preserve_float_range(result, rescale=False)
     elif clip_method == Clip.SCALE_BY_CHUNK:
         data[:] = preserve_float_range(result, rescale=True)
     else:
         data[:] = result
Пример #2
0
    def _high_pass(image: Union[xr.DataArray, np.ndarray],
                   sigma: Union[Number, Tuple[Number]],
                   rescale: bool = False) -> Union[xr.DataArray, np.ndarray]:
        """
        Applies a gaussian high pass filter to an image

        Parameters
        ----------
        image : Union[xr.DataArray, np.ndarray]
            2-d or 3-d image data
        sigma : Union[Number, Tuple[Number]]
            Standard deviation of gaussian kernel
        rescale : bool
            If true scales data by max value, if false clips max values to one

        Returns
        -------
        np.ndarray :
            filtered image of the same shape as the input image
        """

        blurred = GaussianLowPass._low_pass(image, sigma)
        filtered = image - blurred
        filtered = preserve_float_range(filtered, rescale)

        return filtered
Пример #3
0
    def _high_pass(
        image: Union[xr.DataArray, np.ndarray], size: Number, rescale: bool=False
    ) -> np.ndarray:
        """
        Applies a mean high pass filter to an image

        Parameters
        ----------
        image : Union[xr.DataArray, numpy.ndarray]
            2-d or 3-d image data
        size : Union[Number, Tuple[Number]]
            width of the kernel
        rescale : bool
            If true scales data by max value, if false clips max values to one

        Returns
        -------
        np.ndarray[np.float32]:
            Filtered image, same shape as input
        """

        blurred: np.ndarray = uniform_filter(image, size)

        filtered: np.ndarray = image - blurred
        filtered = preserve_float_range(filtered, rescale)

        return filtered
Пример #4
0
    def _low_pass(image: Union[xr.DataArray, np.ndarray],
                  sigma: Union[Number, Tuple[Number]],
                  rescale: bool = False) -> np.ndarray:
        """
        Apply a Gaussian blur operation over a multi-dimensional image.

        Parameters
        ----------
        image : Union[xr.DataArray, np.ndarray]
            2-d or 3-d image data
        sigma : Union[Number, Tuple[Number]]
            Standard deviation of the Gaussian kernel that will be applied. If a float, an
            isotropic kernel will be assumed, otherwise the dimensions of the kernel give (z, y, x)
         rescale : bool
            If true scales data by max value, if false clips max values to one

        Returns
        -------
        np.ndarray :
            Blurred data in same shape as input image, converted to np.float32 dtype.

        """

        filtered = gaussian(image,
                            sigma=sigma,
                            output=None,
                            cval=0,
                            multichannel=False,
                            preserve_range=True,
                            truncate=4.0)

        filtered = preserve_float_range(filtered, rescale)

        return filtered
Пример #5
0
    def run(self,
            stack: ImageStack,
            in_place: bool = False,
            verbose=None,
            n_processes=None) -> 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 : None
            Not used. Elementwise multiply carries out a single vectorized multiplication that
            cannot provide a status bar. Included for consistency with Filter API.
        n_processes : None
            Not used. Elementwise multiplication scales slowly with additional processes due to the
            efficiency of vectorization on a single process. Included for consistency with Filter
            API. All computation happens on the main process.

        Returns
        -------
        ImageStack :
            If in-place is False, return the results of filter as a new stack.  Otherwise return the
            original stack.

        """

        # Align the axes of the multipliers with ImageStack
        mult_array_aligned: np.ndarray = self.mult_array.transpose(
            *stack.xarray.dims).values
        if not in_place:
            stack = deepcopy(stack)

        # stack._data contains the xarray
        stack._data *= mult_array_aligned
        if self.clip_method == Clip.CLIP:
            stack._data = preserve_float_range(stack._data, rescale=False)
        else:
            stack._data = preserve_float_range(stack._data, rescale=True)
        return stack
Пример #6
0
    def run(
        self,
        stack: ImageStack,
        in_place: bool = False,
        verbose: bool = False,
        n_processes: Optional[int] = None,
        *args,
    ) -> 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 :
            If in-place is False, return the results of filter as a new stack.  Otherwise return the
            original stack.

        """
        # Align the axes of the multipliers with ImageStack
        mult_array_aligned: np.ndarray = self.mult_array.transpose(
            *stack.xarray.dims).values
        if not in_place:
            stack = deepcopy(stack)

        # stack._data contains the xarray
        stack._data *= mult_array_aligned
        if self.clip_method == Clip.CLIP:
            stack._data = preserve_float_range(stack._data, rescale=False)
        else:
            stack._data = preserve_float_range(stack._data, rescale=True)
        return stack
Пример #7
0
    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
Пример #8
0
    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":
        """Create an IntensityTable containing synthetic spots with random locations

        Parameters
        ----------
        codebook : Codebook
            starfish codebook object
        num_z :
            number of z-planes to use when localizing spots
        height :
            y dimension of each synthetic plane
        width :
            x dimension of each synthetic plane
        n_spots :
            number of spots to generate
        mean_fluor_per_spot :
            mean number of fluorophores per spot
        mean_photons_per_fluor :
            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)
        intensities[Features.TARGET] = (Features.AXIS, targets)

        return intensities
Пример #9
0
    def apply(
            self,
            func: Callable,
            group_by: Set[Axes]=None,
            in_place=False,
            verbose: bool=False,
            n_processes: Optional[int]=None,
            clip_method: Union[str, Clip]=Clip.CLIP,
            **kwargs
    ) -> "ImageStack":
        """Split the image along a set of axes and apply a function across all the components.  This
        function should yield data of the same dimensionality as the input components.  These
        resulting components are then constituted into an ImageStack and returned.

        Parameters
        ----------
        func : Callable
            Function to apply. must expect a first argument which is a 2d or 3d numpy array and
            return an array of the same shape.
        group_by : Set[Axes]
            Axes to split the data along.  For instance, splitting a 2D array (axes: X, Y; size:
            3, 4) by X results in 3 arrays of size 4.  (default {Axes.ROUND, Axes.CH,
            Axes.ZPLANE})
        in_place : bool
            (Default False) If True, function is executed in place. If n_proc is not 1, the tile or
            volume will be copied once during execution. If false, a new ImageStack object will be
            produced.
        verbose : bool
            If True, report on the percentage completed (default = False) during processing
        n_processes : Optional[int]
            The number of processes to use for apply. If None, uses the output of os.cpu_count()
            (default = None).
        kwargs : dict
            Additional arguments to pass to func
        clip_method : Union[str, Clip]
            (Default Clip.CLIP) Controls the way that data are scaled to retain skimage dtype
            requirements that float data fall in [0, 1].
            Clip.CLIP: data above 1 are set to 1, and below 0 are set to 0
            Clip.SCALE_BY_IMAGE: data above 1 are scaled by the maximum value, with the maximum
            value calculated over the entire ImageStack
            Clip.SCALE_BY_CHUNK: data above 1 are scaled by the maximum value, with the maximum
            value calculated over each slice, where slice shapes are determined by the group_by
            parameters

        Returns
        -------
        ImageStack :
            If inplace is False, return a new ImageStack, otherwise return a reference to the
            original stack with data modified by application of func
        """
        # default grouping is by (x, y) tile
        if group_by is None:
            group_by = {Axes.ROUND, Axes.CH, Axes.ZPLANE}

        if not isinstance(clip_method, (str, Clip)):
            raise TypeError("must pass a Clip method. See Starfish.types.Clip for valid options")

        if not in_place:
            # create a copy of the ImageStack, call apply on that stack with in_place=True
            image_stack = deepcopy(self)
            return image_stack.apply(
                func=func,
                group_by=group_by, in_place=True, verbose=verbose, n_processes=n_processes,
                clip_method=clip_method,
                **kwargs
            )

        # wrapper adds a target `data` parameter where the results from func will be stored
        # data are clipped or scaled by chunk using preserve_float_range if clip_method != 2
        bound_func = partial(ImageStack._in_place_apply, func, clip_method=clip_method)

        # execute the processing workflow
        self.transform(
            func=bound_func,
            group_by=group_by,
            verbose=verbose,
            n_processes=n_processes,
            **kwargs)

        # scale based on values of whole image
        if clip_method == Clip.SCALE_BY_IMAGE:
            self._data = preserve_float_range(self._data, rescale=True)

        return self