def test_bbox_hypervolume_other() -> None: """ Test that we get the expected non-trivial area for bboxes of various dimensions. """ # 1D minp = [4] maxp = [8] expected_area = 4 assert AxisAlignedBoundingBox(minp, maxp).hypervolume == expected_area # 2D minp = [0, 4] maxp = [2, 10] expected_area = 12 # 2 * 6 assert AxisAlignedBoundingBox(minp, maxp).hypervolume == expected_area # 3D minp = [0, 4, 3] maxp = [1, 10, 8] expected_area = 30 # 1 * 6 * 5 assert AxisAlignedBoundingBox(minp, maxp).hypervolume == expected_area # 4D minp = [0, 4, 3, 5] maxp = [1, 10, 8, 9] expected_area = 120 # 1 * 6 * 5 * 4 assert AxisAlignedBoundingBox(minp, maxp).hypervolume == expected_area
def test_bbox_construction_incongruous_shape() -> None: """ Test that construction fails when one or both input coordinates are not a single array dimension (i.e. multi-dimensional shape in numpy.array parlance). """ minp_1dim = (0, ) minp_2dim = ((0, ), (1, )) maxp_1dim = (1, ) maxp_2dim = ((1, ), (2, )) with pytest.raises(ValueError, match=r"One or both vertices provided had " r"more than one array dimension " r"\(min_vertex\.ndim == 2, " r"max_vertex\.ndim == 1\)\."): # noinspection PyTypeChecker AxisAlignedBoundingBox(minp_2dim, maxp_1dim) # type: ignore with pytest.raises(ValueError, match=r"One or both vertices provided had " r"more than one array dimension " r"\(min_vertex\.ndim == 1, " r"max_vertex\.ndim == 2\)\."): # noinspection PyTypeChecker AxisAlignedBoundingBox(minp_1dim, maxp_2dim) # type: ignore with pytest.raises(ValueError, match=r"One or both vertices provided had " r"more than one array dimension " r"\(min_vertex\.ndim == 2, " r"max_vertex\.ndim == 2\)\."): # noinspection PyTypeChecker AxisAlignedBoundingBox(minp_2dim, maxp_2dim) # type: ignore
def test_bbox_equality_other_not_close() -> None: """ Test that other bbox is not equal when bbox is sufficiently different. """ bb1 = AxisAlignedBoundingBox([1, 2, 3], [2, 3, 4]) # Differs in max z bounds. bb2 = AxisAlignedBoundingBox([1, 2, 3], [2, 3, 5]) assert not (bb1 == bb2)
def test_bbox_equality_other_is_copy() -> None: """ Test that a bounding box is equal to an equivalent other bounding box instance. """ bb1 = AxisAlignedBoundingBox([1, 2, 3], [2, 3, 4]) bb2 = AxisAlignedBoundingBox([1, 2, 3], [2, 3, 4]) assert bb1 == bb2
def test_bbox_repr() -> None: """ Test that __repr__ returns without error. """ assert repr(AxisAlignedBoundingBox([0], [1.2])) == \ "<smqtk_image_io.bbox.AxisAlignedBoundingBox " \ "min_vertex=[0] max_vertex=[1.2]>"
def test_bbox_not_equal(m_bbox_eq: mock.MagicMock) -> None: """ Test that non-equality is just calling the __eq__ in AxisAlignedBoundingBox. :param mock.MagicMock m_bbox_eq: """ bb1 = AxisAlignedBoundingBox([1, 2, 3], [2, 3, 4]) bb2 = AxisAlignedBoundingBox([1, 2, 3], [2, 3, 4]) m_bbox_eq.assert_not_called() # noinspection PyStatementEffect bb1 != bb2 m_bbox_eq.assert_called_once_with(bb2)
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)
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)
def test_bbox_hypervolume_1(ndim: int) -> None: """ Test that we get the expected 1-area from various 1-area hyper-cubes. """ minp = [0] * ndim maxp = [1] * ndim expected_area = 1 assert AxisAlignedBoundingBox(minp, maxp).hypervolume == expected_area
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)
def test_bbox_hash() -> None: """ Test expected hashing of bounding box. """ p1 = (0, 1, 2) p2 = (1, 2, 3) expected_hash = hash((p1, p2)) assert hash(AxisAlignedBoundingBox(p1, p2)) == expected_hash
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)
def test_bbox_equality_other_is_close() -> None: """ Test that a bounding box is equal to an equivalent other bounding box instance. """ e = 1e-8 # default absolute tolerance on ``numpy.allclose`` function. bb1 = AxisAlignedBoundingBox([1, 2, 3], [2, 3, 4]) bb2 = AxisAlignedBoundingBox([1 + e, 2 + e, 3 + e], [2 + e, 3 + e, 4 + e]) # Basic array equality is exact, which should show that these are not # strictly equal. # noinspection PyUnresolvedReferences assert (bb1.min_vertex != bb2.min_vertex).all() # noinspection PyUnresolvedReferences assert (bb1.max_vertex != bb2.max_vertex).all() assert bb1 == bb2
def test_bbox_dtype() -> None: """ Test getting the representative dtype of the bounding box, including mix vertex array types """ # int bb = AxisAlignedBoundingBox([0], [1]) assert issubclass(bb.dtype.type, numpy.signedinteger) bb = AxisAlignedBoundingBox(numpy.array([0], dtype=numpy.uint8), numpy.array([1], dtype=numpy.uint8)) assert issubclass(bb.dtype.type, numpy.uint8) bb = AxisAlignedBoundingBox(numpy.array([0], dtype=numpy.uint8), numpy.array([1], dtype=numpy.uint32)) assert issubclass(bb.dtype.type, numpy.uint32) # float bb = AxisAlignedBoundingBox([0.], [1.]) assert issubclass(bb.dtype.type, numpy.float64) bb = AxisAlignedBoundingBox(numpy.array([0], dtype=numpy.float32), numpy.array([1], dtype=numpy.float16)) assert issubclass(bb.dtype.type, numpy.float32) # mixed bb = AxisAlignedBoundingBox([0], [1.0]) assert issubclass(bb.dtype.type, numpy.float64) bb = AxisAlignedBoundingBox([0.0], [1]) assert issubclass(bb.dtype.type, numpy.float64)
def test_bbox_deltas_3d() -> None: """ Test that `deltas` property returns the correct value for an example 1D region. """ minp = [29, 38, 45] maxp = [792, 83, 45] expected = [763, 45, 0] numpy.testing.assert_allclose( AxisAlignedBoundingBox(minp, maxp).deltas, expected)
def test_bbox_ndim(ndim: int) -> None: """ Test that the ``ndim`` property correctly reflects the dimensionality of the coordinates stored. :param ndim: Dimension integer fixture result. """ bb = AxisAlignedBoundingBox([1] * ndim, [2] * ndim) assert bb.ndim == ndim
def test_getstate_format() -> None: """ Test expected __getstate__ format. """ min_v = (4.2, 8.9, 1) max_v = (9.2, 9.0, 48) expected_state = ([4.2, 8.9, 1], [9.2, 9.0, 48]) bb1 = AxisAlignedBoundingBox(min_v, max_v) assert bb1.__getstate__() == expected_state
def test_bbox_construction_maxp_not_greater() -> None: """ Test the check that the max-coordinate must be >= min-coordinate. """ minp = (10, 10) maxp = (11, 9) with pytest.raises(ValueError, match=r"The maximum vertex was not strictly >= the " r"minimum vertex\."): AxisAlignedBoundingBox(minp, maxp)
def setUpClass(cls): cls.gh_image_fp = os.path.join(TEST_DATA_DIR, "grace_hopper.png") cls.gh_file_element = DataFileElement(cls.gh_image_fp) assert cls.gh_file_element.content_type() == 'image/png' cls.gh_cropped_image_fp = \ os.path.join(TEST_DATA_DIR, 'grace_hopper.100x100+100+100.png') cls.gh_cropped_file_element = DataFileElement(cls.gh_cropped_image_fp) assert cls.gh_cropped_file_element.content_type() == 'image/png' cls.gh_cropped_bbox = AxisAlignedBoundingBox([100, 100], [200, 200])
def test_bbox_deltas_4d() -> None: """ Test that `deltas` property returns the correct value for an example 1D region. """ minp = [3] * 4 maxp = [9] * 4 expected = [6] * 4 numpy.testing.assert_allclose( AxisAlignedBoundingBox(minp, maxp).deltas, expected)
def test_load_as_matrix_with_crop_not_in_bounds(self): """ Test that error is raised when crop bbox is not fully within the image bounds. """ inst = PilImageReader() # Nowhere close bb = AxisAlignedBoundingBox([5000, 6000], [7000, 8000]) with pytest.raises(RuntimeError, match=r"Crop provided not within input image\. " r"Image shape: \(512, 600\), crop: "): inst.load_as_matrix(self.gh_file_element, pixel_crop=bb) # Outside left side bb = AxisAlignedBoundingBox([-1, 1], [2, 2]) with pytest.raises(RuntimeError, match=r"Crop provided not within input image\. " r"Image shape: \(512, 600\), crop: "): inst.load_as_matrix(self.gh_file_element, pixel_crop=bb) # Outside top side bb = AxisAlignedBoundingBox([1, -1], [2, 2]) with pytest.raises(RuntimeError, match=r"Crop provided not within input image\. " r"Image shape: \(512, 600\), crop: "): inst.load_as_matrix(self.gh_file_element, pixel_crop=bb) # Outside right side bb = AxisAlignedBoundingBox([400, 400], [513, 600]) with pytest.raises(RuntimeError, match=r"Crop provided not within input image\. " r"Image shape: \(512, 600\), crop: "): inst.load_as_matrix(self.gh_file_element, pixel_crop=bb) # Outside bottom side bb = AxisAlignedBoundingBox([400, 400], [512, 601]) with pytest.raises(RuntimeError, match=r"Crop provided not within input image\. " r"Image shape: \(512, 600\), crop: "): inst.load_as_matrix(self.gh_file_element, pixel_crop=bb)
def test_bbox_equality_other_not_close_enough(m_bbox_rtol: int, m_bbox_atol: int) -> None: """ Test modifying tolerance values :return: """ e = 1e-8 # default absolute tolerance on ``numpy.allclose`` function. bb1 = AxisAlignedBoundingBox([1, 2, 3], [2, 3, 4]) bb2 = AxisAlignedBoundingBox([1 + e, 2 + e, 3 + e], [2 + e, 3 + e, 4 + e]) # Basic array equality is exact, which should show that these are not # strictly equal. # noinspection PyUnresolvedReferences assert (bb1.min_vertex != bb2.min_vertex).all() # noinspection PyUnresolvedReferences assert (bb1.max_vertex != bb2.max_vertex).all() # If we reduce the tolerances, the 1e-8 difference will become intolerable. m_bbox_rtol.return_value = 1.e-10 # type: ignore m_bbox_atol.return_value = 1.e-16 # type: ignore assert not (bb1 == bb2)
def test_setstate_format() -> None: """ Test expected state format compatible with setstate """ state = ([4.2, 8.9, 1], [9.2, 9.0, 48]) expected_min_v = (4.2, 8.9, 1) expected_max_v = (9.2, 9.0, 48) bb = AxisAlignedBoundingBox([0], [1]) bb.__setstate__(state) numpy.testing.assert_allclose(bb.min_vertex, expected_min_v) numpy.testing.assert_allclose(bb.max_vertex, expected_max_v)
def test_bbox_no_intersection() -> None: """ Test that the expected conditions result in no intersection. """ # +-+ # +-+ # +-+ # +-+ bb1 = AxisAlignedBoundingBox([0, 0], [1, 1]) bb2 = AxisAlignedBoundingBox([3, 3], [4, 4]) assert bb1.intersection(bb2) is None assert bb2.intersection(bb1) is None # Edge adjacency is not intersection # +-+-+ # +-+-+ bb1 = AxisAlignedBoundingBox([0, 0], [1, 1]) bb2 = AxisAlignedBoundingBox([1, 0], [2, 1]) assert bb1.intersection(bb2) is None assert bb2.intersection(bb1) is None # +-+ # +-+ # +-+ bb1 = AxisAlignedBoundingBox([0, 0], [1, 1]) bb2 = AxisAlignedBoundingBox([0, 1], [1, 2]) assert bb1.intersection(bb2) is None assert bb2.intersection(bb1) is None
def test_bbox_intersection() -> None: """ Test expected return when there is a valid intersection. """ # With self bb = AxisAlignedBoundingBox([1, 1], [2, 2]) assert bb.intersection(bb) == bb # Diagonal # +---+ # | +---+ # +-|-+ | # +---+ bb1 = AxisAlignedBoundingBox([0, 0], [2, 2]) bb2 = AxisAlignedBoundingBox([1, 1], [3, 3]) expected = AxisAlignedBoundingBox([1, 1], [2, 2]) assert bb1.intersection(bb2) == expected assert bb2.intersection(bb1) == expected # ``other`` fully contained with, and ``other`` fully enclosing # +-----+ # | +-+ | # | +-+ | # +-----+ bb1 = AxisAlignedBoundingBox([3, 2, 8], [6, 5, 11]) bb2 = AxisAlignedBoundingBox([4, 3, 9], [5, 4, 10]) expected = bb2 assert bb1.intersection(bb2) == expected assert bb2.intersection(bb1) == expected
def test_load_as_matrix_with_crop_not_integer(self): """ Test passing a bounding box that is not integer aligned, which should raise an error in the super call. """ inst = PilImageReader() bb = AxisAlignedBoundingBox([100, 100.6], [200, 200.2]) with pytest.raises(ValueError, match=r"Crop bounding box must be " r"composed of integer " r"coordinates\."): inst.load_as_matrix(self.gh_file_element, pixel_crop=bb)
def test_serialize_deserialize_pickle() -> None: """ Test expected state representation. """ min_v = (4.2, 8.9, 1) max_v = (9.2, 9.0, 48) bb1 = AxisAlignedBoundingBox(min_v, max_v) #: :type: AxisAlignedBoundingBox bb2 = pickle.loads(pickle.dumps(bb1)) numpy.testing.assert_allclose(bb2.min_vertex, min_v) numpy.testing.assert_allclose(bb2.max_vertex, max_v)
def test_bbox_construction_incongruous_dimensionality() -> None: """ Test that construction fails if min and max coordinates are not of the same dimensionality. """ minp = (0, 0, 0) maxp = (1, 1, 1, 1) with pytest.raises(ValueError, match=r"Both vertices provided are not the same " r"dimensionality \(min_vertex = 3, " r"max_vertex = 4\)\."): AxisAlignedBoundingBox(minp, maxp)
def setUpClass(cls) -> None: # Initialize test image paths/elements/associated crop boxes. cls.gh_image_fp = os.path.join(TEST_DATA_DIR, "grace_hopper.png") cls.gh_file_element = DataFileElement(cls.gh_image_fp, readonly=True) assert cls.gh_file_element.content_type() == 'image/png' cls.gh_cropped_image_fp = \ os.path.join(TEST_DATA_DIR, 'grace_hopper.100x100+100+100.png') cls.gh_cropped_file_element = \ DataFileElement(cls.gh_cropped_image_fp, readonly=True) assert cls.gh_cropped_file_element.content_type() == 'image/png' cls.gh_cropped_bbox = AxisAlignedBoundingBox([100, 100], [200, 200])
def test_load_as_matrix_crop_zero_volume() -> None: """ Test that a ValueError is raised when a crop bbox is passed with zero volume. """ m_reader = mock.MagicMock(spec=ImageReader) m_data = mock.MagicMock(spec_set=DataElement) crop_bb = AxisAlignedBoundingBox([0, 0], [0, 0]) with pytest.raises(ValueError, match=r"Volume of crop bounding box must be " r"greater than 0\."): ImageReader.load_as_matrix(m_reader, m_data, pixel_crop=crop_bb)