Example #1
0
 def test_in_bounds_zero_crop_area(self) -> None:
     """
     Test that crop is not ``in_bounds`` when it has zero area (undefined).
     """
     # noinspection PyArgumentList
     bb = AxisAlignedBoundingBox([1, 2], [1, 2])
     assert not crop_in_bounds(bb, 4, 6)
Example #2
0
    def test_in_bounds_completely_outside(self) -> None:
        """
        Test that being completely outside the given bounds causes
        ``in_bounds`` to return False.
        """
        bb = AxisAlignedBoundingBox([100, 100], [102, 102])
        assert not crop_in_bounds(bb, 4, 6)

        bb = AxisAlignedBoundingBox([-100, -100], [-98, -98])
        assert not crop_in_bounds(bb, 4, 6)

        bb = AxisAlignedBoundingBox([-100, 100], [-98, 102])
        assert not crop_in_bounds(bb, 4, 6)

        bb = AxisAlignedBoundingBox([100, -100], [102, -98])
        assert not crop_in_bounds(bb, 4, 6)
Example #3
0
    def _load_as_matrix(
        self, data_element: DataElement,
            pixel_crop: Optional[AxisAlignedBoundingBox] = None) \
            -> numpy.ndarray:
        """
        Internal method to be implemented that attempts loading an image
        from the given data element and returning it as an image matrix.

        Pre-conditions:
            - ``pixel_crop`` has a non-zero volume and is composed of integer
              types.

        :param smqtk.representation.DataElement data_element:
            DataElement to load image data from.
        :param None|smqtk.representation.AxisAlignedBoundingBox pixel_crop:
            Optional pixel crop region to load from the given data.  If this
            is provided it must represent a valid sub-region within the loaded
            image, otherwise a RuntimeError is raised.

        :raises RuntimeError: A crop region was specified but did not specify a
            valid sub-region of the image.

        :return: Numpy ndarray of the image data. Specific return format is
            implementation dependant.
        :rtype: numpy.ndarray

        """
        # We may have to add a mode where we use write_temp and load from that
        # if loading large images straight from bytes-in-memory is a problem
        # and that approach actually alleviates anything.

        # Catch and raise alternate IOError exception for readability.
        try:
            #: :type: PIL.Image.Image
            img = PIL.Image.open(BytesIO(data_element.get_bytes()))
        except IOError as ex:
            ex_str = str(ex)
            if 'cannot identify image file' in ex_str:
                raise IOError("Failed to identify image from bytes provided "
                              "by {}".format(data_element))
            else:
                # pass through other exceptions
                raise

        if pixel_crop:
            if not crop_in_bounds(pixel_crop, *img.size):
                raise RuntimeError("Crop provided not within input image. "
                                   "Image shape: {}, crop: {}".format(
                                       img.size, pixel_crop))
            img = img.crop(pixel_crop.min_vertex.tolist() +
                           pixel_crop.max_vertex.tolist())

        # If the loaded image is not already the optionally provided
        # explicit mode, convert it.
        if self._explicit_mode and img.mode != self._explicit_mode:
            img = img.convert(mode=self._explicit_mode)

        # noinspection PyTypeChecker
        return numpy.asarray(img)
Example #4
0
    def test_in_bounds_inside_edges(self) -> None:
        """
        Test that a crop is "in bounds" when contacting the 4 edges of the
        given rectangular bounds.

            +--+
            |  |
            ## |  => (4, 6) image, (2,2) crop
            ## |
            |  |
            +--+

            +##+
            |##|
            |  |  => (4, 6) image, (2,2) crop
            |  |
            |  |
            +--+

            +--+
            |  |
            | ##  => (4, 6) image, (2,2) crop
            | ##
            |  |
            +--+

            +--+
            |  |
            |  |  => (4, 6) image, (2,2) crop
            |  |
            |##|
            +##+

        """
        # noinspection PyArgumentList
        bb = AxisAlignedBoundingBox([0, 2], [2, 4])
        assert crop_in_bounds(bb, 4, 6)

        bb = AxisAlignedBoundingBox([1, 0], [3, 2])
        assert crop_in_bounds(bb, 4, 6)

        bb = AxisAlignedBoundingBox([2, 2], [4, 4])
        assert crop_in_bounds(bb, 4, 6)

        bb = AxisAlignedBoundingBox([1, 4], [3, 6])
        assert crop_in_bounds(bb, 4, 6)
Example #5
0
    def test_in_bounds_crossing_edges(self) -> None:
        """
        Test that ``in_bounds`` returns False when crop crossed the 4 edges.

            +--+
            |  |
           ### |  => (4, 6) image, (3,2) crop
           ### |
            |  |
            +--+

            +--+
            |  |
            | ###  => (4, 6) image, (3,2) crop
            | ###
            |  |
            +--+

             ##
            +##+
            |##|
            |  |  => (4, 6) image, (2,3) crop
            |  |
            |  |
            +--+

            +--+
            |  |
            |  |  => (4, 6) image, (2,3) crop
            |  |
            |##|
            +##+
             ##

        """
        bb = AxisAlignedBoundingBox([-1, 2], [2, 4])
        assert not crop_in_bounds(bb, 4, 6)

        bb = AxisAlignedBoundingBox([2, 2], [5, 4])
        assert not crop_in_bounds(bb, 4, 6)

        bb = AxisAlignedBoundingBox([1, -1], [3, 2])
        assert not crop_in_bounds(bb, 4, 6)

        bb = AxisAlignedBoundingBox([1, 4], [3, 7])
        assert not crop_in_bounds(bb, 4, 6)
Example #6
0
    def test_in_bounds_inside(self) -> None:
        """
        Test that ``in_bounds`` passes when crop inside given rectangle bounds.

            +--+
            |  |
            |##|  => (4, 6) image, (2,2) crop
            |##|
            |  |
            +--+

        """
        bb = AxisAlignedBoundingBox([1, 2], [3, 4])
        assert crop_in_bounds(bb, 4, 8)
Example #7
0
    def _load_as_matrix(
            self,
            data_element: DataElement,
            pixel_crop: AxisAlignedBoundingBox = None) -> np.ndarray:
        """
        Internal method to be implemented that attempts loading an image
        from the given data element and returning it as an image matrix.

        Pre-conditions:
            - ``pixel_crop`` has a non-zero volume and is composed of integer
              types.

        :param smqtk.representation.DataElement data_element:
            DataElement to load image data from.
        :param None|smqtk.representation.AxisAlignedBoundingBox pixel_crop:
            Optional pixel crop region to load from the given data.  If this
            is provided it must represent a valid sub-region within the loaded
            image, otherwise a RuntimeError is raised.

        :raises RuntimeError: A crop region was specified but did not specify a
            valid sub-region of the image.

        :return: Numpy ndarray of the image data. Specific return format is
            implementation dependant.
        :rtype: np.ndarray

        """
        if data_element.is_empty():
            raise ValueError(
                "GdalImageReader cannot load 0-sized data (no bytes in {}).".
                format(data_element))
        load_cm = self.LOAD_METHOD_CONTEXTMANAGERS[self._load_method]
        with load_cm(data_element) as gdal_ds:  # type: gdal.Dataset
            img_width = gdal_ds.RasterXSize
            img_height = gdal_ds.RasterYSize

            # GDAL wants [x, y, width, height] as the first 4 positional
            # arguments to ``ReadAsArray``.
            xywh = [0, 0, img_width, img_height]
            if pixel_crop:
                if not crop_in_bounds(pixel_crop, img_width, img_height):
                    raise RuntimeError("Crop provided not within input image. "
                                       "Image shape: {}, crop: {}".format(
                                           (img_width, img_height),
                                           pixel_crop))
                # This is testing faster than ``np.concatenate``.
                xywh = \
                    pixel_crop.min_vertex.tolist() + pixel_crop.deltas.tolist()

            # Select specific channels if they are present in this dataset, or
            # just get all of them
            if self._channel_order is not None:
                assert self._channel_order_gci is not None, (
                    "When a channel-order is set, the GCI equivalent should "
                    "also be set.")
                # Map raster bands from CI value to band index.
                # - GDAL uses 1-based indexing.
                band_ci_to_idx = {
                    gdal_ds.GetRasterBand(b_i).GetColorInterpretation(): b_i
                    for b_i in range(1, gdal_ds.RasterCount + 1)
                }
                gci_diff = (set(
                    self._channel_order_gci).difference(band_ci_to_idx))
                if gci_diff:
                    raise RuntimeError(
                        "Data element did not provide channels required to "
                        "satisfy requested channel order {}.  "
                        "Data had bands: {} (missing {}).".format(
                            map_gci_list_to_names(self._channel_order_gci),
                            map_gci_list_to_names(band_ci_to_idx),
                            map_gci_list_to_names(gci_diff)))
                # Initialize a matrix to read band image data into
                # TODO: Handle when there are no bands?
                band_dtype = gdal_array.GDALTypeCodeToNumericTypeCode(
                    gdal_ds.GetRasterBand(1).DataType)
                if len(self._channel_order_gci) > 1:
                    img_mat = np.ndarray(
                        [xywh[3], xywh[2],
                         len(self._channel_order_gci)],
                        dtype=band_dtype)
                    for i, gci in enumerate(self._channel_order_gci):
                        #: :type: gdal.Band
                        b = gdal_ds.GetRasterBand(band_ci_to_idx[gci])
                        b.ReadAsArray(*xywh, buf_obj=img_mat[:, :, i])
                else:
                    img_mat = np.ndarray([xywh[3], xywh[2]], dtype=band_dtype)
                    gci = self._channel_order_gci[0]
                    b = gdal_ds.GetRasterBand(band_ci_to_idx[gci])
                    b.ReadAsArray(*xywh, buf_obj=img_mat)
            else:
                img_mat = gdal_ds.ReadAsArray(*xywh)
                if img_mat.ndim > 2:
                    # Transpose into [height, width, channel] format.
                    img_mat = img_mat.transpose(1, 2, 0)

        return img_mat