Esempio n. 1
0
    def __init__(
            self,
            pixel_ticks: Union[Mapping[Axes, ArrayLike[int]], Mapping[str, ArrayLike[int]]],
            physical_ticks: Union[Mapping[Coordinates, ArrayLike[Number]],
                                  Mapping[str, ArrayLike[Number]]],
            masks: Sequence[MaskData],
            log: Optional[Log],
    ):
        self._pixel_ticks: Mapping[Axes, ArrayLike[int]] = _normalize_pixel_ticks(pixel_ticks)
        self._physical_ticks: Mapping[Coordinates, ArrayLike[Number]] = _normalize_physical_ticks(
            physical_ticks)
        self._masks: MutableMapping[int, MaskData] = {}
        self._log: Log = log or Log()

        for ix, mask_data in enumerate(masks):
            if mask_data.binary_mask.ndim not in (2, 3):
                raise TypeError(f"expected 2 or 3 dimensions; got {mask_data.binary_mask.ndim}")
            if mask_data.binary_mask.dtype != bool:
                raise ValueError(f"expected dtype of bool; got {mask_data.binary_mask.dtype}")

            self._masks[ix] = mask_data

        if len(self._pixel_ticks) != len(self._physical_ticks):
            raise ValueError(
                "pixel_ticks should have the same cardinality as physical_ticks")
        for axis, coord in zip(*_get_axes_names(len(self._pixel_ticks))):
            if axis not in self._pixel_ticks:
                raise ValueError(f"pixel ticks missing {axis.value} data")
            if coord not in self._physical_ticks:
                raise ValueError(f"physical coordinate ticks missing {coord.value} data")
            if len(self._pixel_ticks[axis]) != len(self._physical_ticks[coord]):
                raise ValueError(
                    f"pixel ticks for {axis.name} does not have the same cardinality as physical "
                    f"coordinates ticks for {coord.name}")
Esempio n. 2
0
    def from_label_array_and_ticks(
        cls,
        array: np.ndarray,
        pixel_ticks: Optional[Union[Mapping[Axes, ArrayLike[int]],
                                    Mapping[str, ArrayLike[int]]]],
        physical_ticks: Union[Mapping[Coordinates, ArrayLike[Number]],
                              Mapping[str, ArrayLike[Number]]],
        log: Optional[Log],
    ) -> "BinaryMaskCollection":
        """Constructs a BinaryMaskCollection from an array containing the labels, a set of physical
        coordinates, and an optional log of how this label image came to be.  Masks are cropped to
        the smallest size that contains the non-zero values, but pixel and physical coordinates
        ticks are retained.  Masks extracted from BinaryMaskCollections will be cropped.  To extract
        masks sized to the original label image, use
        :py:meth:`starfish.morphology.BinaryMaskCollection.uncropped_mask`.

        Parameters
        ----------
        array : np.ndarray
            A 2D or 3D array containing the labels.  The ordering of the axes must be Y, X for 2D
            images and ZPLANE, Y, X for 3D images.
        pixel_ticks : Optional[Union[Mapping[Axes, ArrayLike[int]], Mapping[str, ArrayLike[int]]]]
            A map from the axis to the values for that axis.  For any axis that exist in the array
            but not in pixel_ticks, the pixel coordinates are assigned from 0..N-1, where N is
            the size along that axis.
        physical_ticks : Union[Mapping[Coordinates, ArrayLike[Number]], Mapping[str,
        ArrayLike[Number]]]
            A map from the physical coordinate type to the values for axis.  For 2D label images,
            X and Y physical coordinates must be provided.  For 3D label images, Z physical
            coordinates must also be provided.
        log : Optional[Log]
            A log of how this label image came to be.

        Returns
        -------
        masks : BinaryMaskCollection
            Masks generated from the label image.
        """
        # normalize the pixel coordinates to Mapping[Axes, ArrayLike[int]]
        pixel_ticks = _normalize_pixel_ticks(pixel_ticks)
        # normalize the physical coordinates to Mapping[Coordinates, ArrayLike[Number]]
        physical_ticks = _normalize_physical_ticks(physical_ticks)

        for ix, (axis, coord) in enumerate(zip(*_get_axes_names(array.ndim))):
            # We verify that the physical coordinate array's size matches the array's size.  In
            # BinaryMaskCollection's constructor, it is verified that the pixel coordinate array's
            # size matches that of the physical coordinate array's size.
            if coord not in physical_ticks:
                raise ValueError(
                    f"Missing physical coordinates for {coord.name}")
            if len(physical_ticks[coord]) != array.shape[ix]:
                raise ValueError(
                    f"The size of the array for physical coordinates for {coord.name} "
                    f"({len(physical_ticks[coord])} does not match the array's shape "
                    f"({array.shape[ix]}).")
            if axis not in pixel_ticks:
                pixel_ticks[axis] = np.arange(0, array.shape[ix])

        props = regionprops(array)

        masks: Sequence[MaskData] = [
            MaskData(prop.image, prop.bbox[:array.ndim], prop)
            for prop in props
        ]
        log = deepcopy(log)

        return cls(
            pixel_ticks,
            physical_ticks,
            masks,
            log,
        )
Esempio n. 3
0
    def from_binary_arrays_and_ticks(
        cls,
        arrays: Sequence[np.ndarray],
        pixel_ticks: Optional[Union[Mapping[Axes, ArrayLike[int]],
                                    Mapping[str, ArrayLike[int]]]],
        physical_ticks: Union[Mapping[Coordinates, ArrayLike[Number]],
                              Mapping[str, ArrayLike[Number]]],
        log: Optional[Log],
    ) -> "BinaryMaskCollection":
        """Constructs a BinaryMaskCollection from an array containing the labels, a set of physical
        coordinates, and an optional log of how this label image came to be.  Masks are cropped to
        the smallest size that contains the non-zero values, but pixel and physical coordinates
        ticks are retained.  Masks extracted from BinaryMaskCollections will be cropped.  To extract
        masks sized to the original label image, use
        :py:meth:`starfish.morphology.BinaryMaskCollection.uncropped_mask`.

        Parameters
        ----------
        arrays : Sequence[np.ndarray]
            A set of 2D or 3D binary arrays.  The ordering of the axes must be Y, X for 2D images
            and ZPLANE, Y, X for 3D images.  The arrays must have identical sizes and match the
            sizes of pixel_ticks and physical_ticks.
        pixel_ticks : Optional[Union[Mapping[Axes, ArrayLike[int]], Mapping[str, ArrayLike[int]]]]
            A map from the axis to the values for that axis.  For any axis that exist in the array
            but not in pixel_ticks, the pixel coordinates are assigned from 0..N-1, where N is
            the size along that axis.
        physical_ticks : Union[Mapping[Coordinates, ArrayLike[Number]], Mapping[str,
        ArrayLike[Number]]]
            A map from the physical coordinate type to the values for axis.  For 2D label images,
            X and Y physical coordinates must be provided.  For 3D label images, Z physical
            coordinates must also be provided.
        log : Optional[Log]
            A log of how this label image came to be.

        Returns
        -------
        masks : BinaryMaskCollection
            Masks generated from the label image.
        """
        array_shapes = set(array.shape for array in arrays)
        array_dtypes = set(array.dtype for array in arrays)
        if len(array_shapes) > 1:
            raise ValueError("all masks must be identically sized")
        for array_dtype in array_dtypes:
            if array_dtype != np.bool:
                raise TypeError("arrays must be binary data")

        # normalize the pixel coordinates to Mapping[Axes, ArrayLike[int]]
        pixel_ticks = _normalize_pixel_ticks(pixel_ticks)
        # normalize the physical coordinates to Mapping[Coordinates, ArrayLike[Number]]
        physical_ticks = _normalize_physical_ticks(physical_ticks)

        # If we have 1 or more arrays, verify that the physical coordinate array's size matches the
        # array's size.  In BinaryMaskCollection's constructor, it is verified that the pixel
        # tick array's size matches that of the physical coordinate array's size.
        if len(array_shapes) == 1:
            array_shape = next(iter(array_shapes))

            # verify that we don't have extra
            expected_axes, expected_physical_coords = _get_axes_names(
                len(array_shape))
            actual_real_coordinates_provided = set(expected_physical_coords)
            actual_real_coordinates_provided.intersection_update(
                set(physical_ticks.keys()))

            if len(array_shape) != len(actual_real_coordinates_provided):
                raise ValueError(
                    f"physical coordinates provided for {len(actual_real_coordinates_provided)} "
                    f"axes ({actual_real_coordinates_provided}), but the data has "
                    f"{len(array_shape)} axes")

            for ix, (axis, coord) in enumerate(
                    zip(*_get_axes_names(len(array_shape)))):
                if (coord in physical_ticks
                        and len(physical_ticks[coord]) != array_shape[ix]):
                    raise ValueError(
                        f"The size of the array for physical coordinates for {coord.name} "
                        f"({len(physical_ticks[coord])} does not match the array's shape "
                        f"({array_shape[ix]}).")

        # Add all pixel ticks not present.
        for axis, coord in zip(*_get_axes_names(3)):
            if coord in physical_ticks and axis not in pixel_ticks:
                pixel_ticks[axis] = np.arange(0, len(physical_ticks[coord]))

        masks: MutableSequence[MaskData] = list()
        for array in arrays:
            selection_range: Tuple[
                slice, ...] = BinaryMaskCollection._crop_mask(array)

            masks.append(
                MaskData(
                    array[selection_range],
                    tuple(selection.start for selection in selection_range),
                    None))

        log = deepcopy(log)

        return cls(
            pixel_ticks,
            physical_ticks,
            masks,
            log,
        )
Esempio n. 4
0
    def from_label_array_and_ticks(
        cls,
        array: np.ndarray,
        pixel_ticks: Optional[Union[Mapping[Axes, ArrayLike[int]],
                                    Mapping[str, ArrayLike[int]]]],
        physical_ticks: Union[Mapping[Coordinates, ArrayLike[Number]],
                              Mapping[str, ArrayLike[Number]]],
        log: Optional[Log],
    ) -> "LabelImage":
        """Constructs a LabelImage from an array containing the labels, a set of physical
        coordinates, and an optional log of how this label image came to be.

        Parameters
        ----------
        array : np.ndarray
            A 2D or 3D array containing the labels.  The ordering of the axes must be Y, X for 2D
            images and ZPLANE, Y, X for 3D images.
        pixel_ticks : Optional[Union[Mapping[Axes, ArrayLike[int]], Mapping[str, ArrayLike[int]]]]
            A map from the axis to the values for that axis.  For any axis that exist in the array
            but not in pixel_coordinates, the pixel coordinates are assigned from 0..N-1, where N is
            the size along that axis.
        physical_ticks : Union[Mapping[Coordinates, ArrayLike[Number]], Mapping[str,
        ArrayLike[Number]]]
            A map from the physical coordinate type to the values for axis.  For 2D label images,
            X and Y physical coordinates must be provided.  For 3D label images, Z physical
            coordinates must also be provided.
        log : Optional[Log]
            A log of how this label image came to be.
        """
        # normalize the pixel coordinates to Mapping[Axes, ArrayLike[int]]
        pixel_ticks = _normalize_pixel_ticks(pixel_ticks)
        # normalize the physical coordinates to Mapping[Coordinates, ArrayLike[Number]]
        physical_ticks = _normalize_physical_ticks(physical_ticks)

        img_axes, img_coords = _get_axes_names(array.ndim)
        xr_axes = [axis.value for axis in img_axes]
        try:
            xr_coords: MutableMapping[Hashable, Any] = {
                coord.value: (axis.value, physical_ticks[coord])
                for axis, coord in zip(img_axes, img_coords)
            }
        except KeyError as ex:
            raise KeyError(
                f"missing physical coordinates {ex.args[0]}") from ex

        for ix, axis in enumerate(img_axes):
            xr_coords[axis.value] = pixel_ticks.get(
                axis, np.arange(0, array.shape[ix]))

        dataarray = xr.DataArray(
            array,
            dims=xr_axes,
            coords=xr_coords,
        )
        dataarray.attrs.update({
            AttrKeys.LOG: (log or Log()).encode(),
            AttrKeys.DOCTYPE: DOCTYPE_STRING,
            AttrKeys.VERSION: str(CURRENT_VERSION),
        })

        return LabelImage(dataarray)