def it_knows_its_biggest_tissue_box_mask( self, request, tmpdir, RgbToGrayscale_, OtsuThreshold_, BinaryDilation_, RemoveSmallHoles_, RemoveSmallObjects_, ): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") regions = [ Region(index=0, area=33, bbox=(0, 0, 2, 2), center=(0.5, 0.5)) ] main_tissue_areas_mask_filters_ = property_mock( request, Slide, "_main_tissue_areas_mask_filters") main_tissue_areas_mask_filters_.return_value = Compose([ RgbToGrayscale_, OtsuThreshold_, BinaryDilation_, RemoveSmallHoles_, RemoveSmallObjects_, ]) regions_from_binary_mask = function_mock( request, "histolab.slide.Slide._regions_from_binary_mask") regions_from_binary_mask.return_value = regions biggest_regions_ = function_mock( request, "histolab.slide.Slide._biggest_regions") biggest_regions_.return_value = regions region_coordinates_ = function_mock( request, "histolab.slide.Slide._region_coordinates") region_coordinates_.return_values = CoordinatePair(0, 0, 2, 2) polygon_to_mask_array_ = function_mock( request, "histolab.util.polygon_to_mask_array") polygon_to_mask_array_( (1000, 1000), CoordinatePair(0, 0, 2, 2)).return_value = [[True, True], [False, True]] biggest_mask_tissue_box = slide.biggest_tissue_box_mask region_coordinates_.assert_called_once_with(slide, regions[0]) biggest_regions_.assert_called_once_with(slide, regions, n=1) polygon_to_mask_array_.assert_called_once_with((1000, 1000), CoordinatePair(x_ul=0, y_ul=0, x_br=2, y_br=2)) np.testing.assert_almost_equal(biggest_mask_tissue_box.todense(), np.zeros((500, 500)))
def it_knows_its_coords(self): _coords = CoordinatePair(0, 0, 50, 50) tile = Tile(None, _coords, 0) coords = tile.coords assert coords == _coords
def it_knows_its_region_coordinates(self): region = Region(index=0, area=14, bbox=(0, 1, 1, 2), center=(0.5, 0.5)) slide = Slide("a/b", "c/d") region_coords_ = slide._region_coordinates(region) assert region_coords_ == CoordinatePair(x_ul=1, y_ul=0, x_br=2, y_br=1)
def it_can_generate_random_coordinates(self, request, tmpdir): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") _box_mask_lvl = method_mock(request, RandomTiler, "box_mask_lvl") _box_mask_lvl.return_value = SparseArrayMock.ONES_500X500_BOOL _tile_size = property_mock(request, RandomTiler, "tile_size") _tile_size.return_value = (128, 128) _np_random_choice1 = function_mock(request, "numpy.random.choice") _np_random_choice1.return_value = 0 _np_random_choice2 = function_mock(request, "numpy.random.choice") _np_random_choice2.return_value = 0 _scale_coordinates = function_mock(request, "histolab.tiler.scale_coordinates") random_tiler = RandomTiler((128, 128), 10, 0) random_tiler._random_tile_coordinates(slide) _box_mask_lvl.assert_called_once_with(random_tiler, slide) _tile_size.assert_has_calls([call((128, 128))]) _scale_coordinates.assert_called_once_with( reference_coords=CoordinatePair(x_ul=0, y_ul=0, x_br=128, y_br=128), reference_size=(500, 500), target_size=(500, 500), )
def it_knows_if_is_is_almost_white(self, tile_img, expected_result): coords = CoordinatePair(0, 512, 0, 512) tile = Tile(tile_img, coords) is_almost_white = tile._is_almost_white assert is_almost_white == expected_result
def but_it_has_wrong_image_type(self): """This test simulates a wrong user behaviour, using a None object instead of a PIL Image for image param""" with pytest.raises(AttributeError) as err: tile = Tile(None, CoordinatePair(0, 0, 50, 50), 0) tile.has_enough_tissue() assert isinstance(err.value, AttributeError) assert str(err.value) == "'NoneType' object has no attribute 'convert'"
def it_constructs_from_args(self, request): _init = initializer_mock(request, Tile) _image = PILIMG.RGBA_COLOR_50X50_155_0_0 _level = 0 _coords = CoordinatePair(0, 0, 50, 50) tile = Tile(_image, _coords, _level) _init.assert_called_once_with(ANY, _image, _coords, _level) assert isinstance(tile, Tile)
def and_doesnt_raise_error_with_wrong_coordinates(self, request, tmpdir): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") coords = CoordinatePair(5800, 6000, 5800, 6000) _grid_coordinates_generator = method_mock( request, GridTiler, "_grid_coordinates_generator") _grid_coordinates_generator.return_value = [coords] grid_tiler = GridTiler((10, 10)) generated_tiles = list(grid_tiler._grid_tiles_generator(slide)) assert len(generated_tiles) == 0 _grid_coordinates_generator.assert_called_once_with(grid_tiler, slide)
class Describe_Slide(object): def it_constructs_from_args(self, request): _init_ = initializer_mock(request, Slide) _slide_path = "/foo/bar/myslide.svs" _processed_path = "/foo/bar/myslide/processed" slide = Slide(_slide_path, _processed_path) _init_.assert_called_once_with(ANY, _slide_path, _processed_path) assert isinstance(slide, Slide) def but_it_has_wrong_slide_path_type(self): """This test simulates a wrong user behaviour, using a None object instead of a str, or a path as slide_path param""" with pytest.raises(TypeError) as err: slide = Slide(None, "prc") slide.name assert isinstance(err.value, TypeError) assert (str(err.value) == "expected str, bytes or os.PathLike object, not NoneType") def or_it_has_wrong_processed_path(self, request): """This test simulates a wrong user behaviour, using a None object instead of a str, or a path as processed_path param""" _resampled_dimensions = method_mock(request, Slide, "_resampled_dimensions") _resampled_dimensions.return_value = (1, 2, 3, 4) with pytest.raises(TypeError) as err: slide = Slide("path", None) slide.scaled_image_path(32) assert isinstance(err.value, TypeError) assert (str(err.value) == "expected str, bytes or os.PathLike object, not NoneType") def it_generates_the_correct_breadcumb(self, request, breadcumb_fixture): ( resampled_dims, dir_path, slide_path, proc_path, scale_factor, expected_path, ) = breadcumb_fixture _resampled_dimensions = method_mock(request, Slide, "_resampled_dimensions") _resampled_dimensions.return_value = resampled_dims slide = Slide(slide_path, proc_path) _breadcumb = slide._breadcumb(dir_path, scale_factor) assert _breadcumb == expected_path def it_knows_its_name(self, slide_name_fixture): _slide_path, expected_value = slide_name_fixture slide = Slide(_slide_path, "processed/") name = slide.name assert name == expected_value def it_knows_its_scaled_image_path(self, scaled_img_path_fixture, resampled_dims_): slide_path, proc_path, slide_dims, expected_value = scaled_img_path_fixture resampled_dims_.return_value = slide_dims slide = Slide(slide_path, proc_path) scaled_img_path = slide.scaled_image_path(scale_factor=22) assert scaled_img_path == expected_value def it_knows_its_thumbnails_path(self, resampled_dims_): slide_path, proc_path, slide_dims, expected_value = ( "/foo/bar/myslide.svs", "/foo/bar/myslide/processed", (345, 111, 333, 444), "/foo/bar/myslide/processed/thumbnails/myslide.png", ) resampled_dims_.return_value = slide_dims slide = Slide(slide_path, proc_path) thumbnail_path = slide.thumbnail_path assert thumbnail_path == expected_value def it_knows_its_wsi_extension(self, slide_ext_fixture): slide_path, expected_value = slide_ext_fixture slide = Slide(slide_path, "processed") _ext = slide._extension assert _ext == expected_value def it_knows_its_dimensions(self, tmpdir): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") slide_dims = slide.dimensions assert slide_dims == (500, 500) def it_knows_its_resampled_dimensions(self, dimensions_): """This test prove that given the dimensions (mock object here), it does the correct maths operations: `large_w, large_h = self.dimensions` `new_w = math.floor(large_w / self._scale_factor)` `new_h = math.floor(large_h / self._scale_factor)` `return large_w, large_h, new_w, new_h` """ dimensions_.return_value = (300, 500) slide = Slide("/a/b/foo", "processed") _resampled_dims = slide._resampled_dimensions(scale_factor=32) assert _resampled_dims == (300, 500, 9, 15) def but_it_raises_zero_division_error_when_scalefactor_is_0( self, dimensions_): """Considering the teset above, this one prove that a wrong behaviour of the user can cause a zerodivision error. In this case the scale_factor=0 generates the ZeroDivision Exception """ dimensions_.return_value = (300, 500) slide = Slide("/a/b/foo", "processed") with pytest.raises(ZeroDivisionError) as err: slide._resampled_dimensions(scale_factor=0) assert isinstance(err.value, ZeroDivisionError) assert str(err.value) == "division by zero" def it_knows_its_resampled_array(self, tmpdir, resampled_dims_): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") resampled_dims_.return_value = (100, 200, 300, 400) resampled_array = slide.resampled_array(scale_factor=32) assert type(resampled_array) == np.ndarray assert resampled_array.shape == (400, 300, 3) def it_knows_its_thumbnail_size(self, tmpdir): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") thumb_size = slide._thumbnail_size assert thumb_size == (500, 500) def it_creates_a_correct_slide_object(self, tmpdir): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_50X50_RGBA_COLOR_155_0_0 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") _wsi = slide._wsi assert type(_wsi) in (openslide.OpenSlide, openslide.ImageSlide) def but_it_raises_an_exception_if_file_not_found(self): with pytest.raises(FileNotFoundError) as err: slide = Slide("wrong/path/fake.wsi", "processed") slide._wsi assert isinstance(err.value, FileNotFoundError) assert str(err.value) == "The wsi path resource doesn't exist" def or_it_raises_an_PIL_exception(self, tmpdir): slide_path = tmpdir.mkdir("sub").join("hello.txt") slide_path.write("content") with pytest.raises(PIL.UnidentifiedImageError) as err: slide = Slide(os.path.join(slide_path), "processed") slide._wsi assert isinstance(err.value, PIL.UnidentifiedImageError) assert (str(err.value) == f"cannot identify image file '{os.path.join(slide_path)}'") def it_can_resample_itself(self, tmpdir, resampled_dims_): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") resampled_dims_.return_value = (100, 200, 300, 400) _resample = slide._resample(32) # image array assertions assert type(_resample[1]) == np.ndarray # ---The np array shape should be (new_h X new_w X channels),--- # ---in this case (look at resampled_dims mock) the new_h is 400--- # ---the new_w is 300 and the color channels of the image are 3--- assert _resample[1].shape == (400, 300, 3) # ---Here we prove that the 3 channels are compliant with the color--- # ---definition and that each channel is a np.array (400x300) filled--- # ---with the related color expressed during the image creation--- np.testing.assert_almost_equal(_resample[1][:, :, 0], np.full((400, 300), 155)) np.testing.assert_almost_equal(_resample[1][:, :, 1], np.full((400, 300), 249)) np.testing.assert_almost_equal(_resample[1][:, :, 2], np.full((400, 300), 240)) # PIL image assertions assert type(_resample[0]) == PIL.Image.Image assert _resample[0].size == (300, 400) assert _resample[0].width == 300 assert _resample[0].height == 400 assert _resample[0].mode == "RGB" def it_resamples_with_the_correct_scale_factor(self, tmpdir, resampled_dims_): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") resampled_dims_.return_value = (500, 500, 15, 15) _resample = slide._resample(32) assert _resample[1].shape == (math.floor(500 / 32), math.floor(500 / 32), 3) def it_can_save_scaled_image(self, tmpdir, resampled_dims_): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, os.path.join(tmp_path_, "processed")) resampled_dims_.return_value = (100, 200, 300, 400) slide.save_scaled_image(32) assert slide.scaled_image_path(32) == os.path.join( tmp_path_, "processed", "mywsi-32x-100x200-300x400.png") assert os.path.exists( os.path.join(tmp_path_, slide.scaled_image_path(32))) def it_can_save_thumbnail(self, tmpdir, resampled_dims_): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, os.path.join(tmp_path_, "processed")) resampled_dims_.return_value = (100, 200, 300, 400) slide.save_thumbnail() assert slide.thumbnail_path == os.path.join(tmp_path_, "processed", "thumbnails", "mywsi.png") assert os.path.exists(os.path.join(tmp_path_, slide.thumbnail_path)) def it_knows_tissue_areas_mask_filters_composition( self, RgbToGrayscale_, OtsuThreshold_, BinaryDilation_, RemoveSmallHoles_, RemoveSmallObjects_, ): slide = Slide("/a/b", "c/d") main_tissue_areas_mask_filters_ = slide._main_tissue_areas_mask_filters RgbToGrayscale_.assert_called_once() OtsuThreshold_.assert_called_once() BinaryDilation_.assert_called_once() RemoveSmallHoles_.assert_called_once() RemoveSmallObjects_.assert_called_once() assert main_tissue_areas_mask_filters_.filters == [ RgbToGrayscale_(), OtsuThreshold_(), BinaryDilation_(), RemoveSmallHoles_(), RemoveSmallObjects_(), ] assert type(main_tissue_areas_mask_filters_) == Compose def it_knows_regions_from_binary_mask(self, request): binary_mask = np.array([[True, False], [True, True]]) label = function_mock(request, "histolab.slide.label") regionprops = function_mock(request, "histolab.slide.regionprops") RegionProps = namedtuple("RegionProps", ("area", "bbox", "centroid")) regions_props = [ RegionProps(3, (0, 0, 2, 2), (0.6666666666666666, 0.3333333333333333)) ] regionprops.return_value = regions_props label(binary_mask).return_value = [[0, 1], [1, 1]] slide = Slide("/a/b", "c/d") regions_from_binary_mask_ = slide._regions_from_binary_mask( binary_mask) regionprops.assert_called_once_with(label(binary_mask)) assert type(regions_from_binary_mask_) == list assert len(regions_from_binary_mask_) == 1 assert type(regions_from_binary_mask_[0]) == Region assert regions_from_binary_mask_ == [ Region( index=0, area=regions_props[0].area, bbox=regions_props[0].bbox, center=regions_props[0].centroid, ) ] def it_knows_its_biggest_regions(self): regions = [ Region(index=0, area=14, bbox=(0, 0, 2, 2), center=(0.5, 0.5)), Region(index=1, area=2, bbox=(0, 0, 2, 2), center=(0.5, 0.5)), Region(index=2, area=5, bbox=(0, 0, 2, 2), center=(0.5, 0.5)), Region(index=3, area=10, bbox=(0, 0, 2, 2), center=(0.5, 0.5)), ] slide = Slide("a/b", "c/d") biggest_regions = slide._biggest_regions(regions, 2) assert biggest_regions == [regions[0], regions[3]] @pytest.mark.parametrize("n", (0, 6)) def but_it_raises_an_error_when_n_is_not_between_1_and_number_of_regions( self, n): regions = [ Region(i, i + 1, (0, 0, 2, 2), (0.5, 0.5)) for i in range(4) ] slide = Slide("a/b", "c/d") with pytest.raises(ValueError) as err: slide._biggest_regions(regions, n) assert str( err.value) == f"n should be between 1 and {len(regions)}, got {n}" def it_knows_its_region_coordinates(self): region = Region(index=0, area=14, bbox=(0, 1, 1, 2), center=(0.5, 0.5)) slide = Slide("a/b", "c/d") region_coords_ = slide._region_coordinates(region) assert region_coords_ == CoordinatePair(x_ul=1, y_ul=0, x_br=2, y_br=1) def it_knows_its_biggest_tissue_box_mask( self, request, tmpdir, RgbToGrayscale_, OtsuThreshold_, BinaryDilation_, RemoveSmallHoles_, RemoveSmallObjects_, ): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") regions = [ Region(index=0, area=33, bbox=(0, 0, 2, 2), center=(0.5, 0.5)) ] main_tissue_areas_mask_filters_ = property_mock( request, Slide, "_main_tissue_areas_mask_filters") main_tissue_areas_mask_filters_.return_value = Compose([ RgbToGrayscale_, OtsuThreshold_, BinaryDilation_, RemoveSmallHoles_, RemoveSmallObjects_, ]) regions_from_binary_mask = function_mock( request, "histolab.slide.Slide._regions_from_binary_mask") regions_from_binary_mask.return_value = regions biggest_regions_ = function_mock( request, "histolab.slide.Slide._biggest_regions") biggest_regions_.return_value = regions region_coordinates_ = function_mock( request, "histolab.slide.Slide._region_coordinates") region_coordinates_.return_values = CoordinatePair(0, 0, 2, 2) polygon_to_mask_array_ = function_mock( request, "histolab.util.polygon_to_mask_array") polygon_to_mask_array_( (1000, 1000), CoordinatePair(0, 0, 2, 2)).return_value = [[True, True], [False, True]] biggest_mask_tissue_box = slide.biggest_tissue_box_mask region_coordinates_.assert_called_once_with(slide, regions[0]) biggest_regions_.assert_called_once_with(slide, regions, n=1) polygon_to_mask_array_.assert_called_once_with((1000, 1000), CoordinatePair(x_ul=0, y_ul=0, x_br=2, y_br=2)) np.testing.assert_almost_equal(biggest_mask_tissue_box.todense(), np.zeros((500, 500))) @pytest.mark.skipif(not on_ci() or is_win32(), reason="Only run on CIs; hangs on Windows CIs") def it_can_show_its_thumbnail(self, tmpdir): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") slide.save_thumbnail() assert ImageShow.show(PIL.Image.open(slide.thumbnail_path)) def but_it_raises_error_when_it_doesnt_exist(self): slide = Slide("a/b", "processed") with pytest.raises(FileNotFoundError) as err: slide.show() assert (str(err.value) == "Cannot display the slide thumbnail:[Errno 2] No such file or " "directory: 'processed/thumbnails/b.png'") def it_knows_its_level_dimensions(self, tmpdir): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") level_dimensions = slide.level_dimensions(level=0) assert level_dimensions == (500, 500) def but_it_raises_expection_when_level_does_not_exist(self, tmpdir): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") with pytest.raises(ValueError) as err: slide.level_dimensions(level=3) assert str(err.value ) == "Level 3 not available. Number of available levels: 1" def it_knows_if_coords_are_valid(self, valid_coords_fixture, tmpdir): coords, expected_result = valid_coords_fixture tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") _are_valid = slide._are_valid_coords(coords) assert type(_are_valid) == bool assert _are_valid == expected_result # fixtures ------------------------------------------------------- @pytest.fixture(params=[("/a/b/mywsi.svs", ".svs"), ("/a/b/mywsi.34s", ".34s")]) def slide_ext_fixture(self, request): slide_path, expected_value = request.param return slide_path, expected_value @pytest.fixture(params=[ ( "/foo/bar/myslide.svs", "/foo/bar/myslide/processed", (345, 111, 333, 444), "/foo/bar/myslide/processed/myslide-22x-345x111-333x444.png", ), ( "/foo/bar/myslide2.svs", "/foo/bar/myslide/processed", (345, 111, None, None), "/foo/bar/myslide/processed/myslide2-22x-345x111-NonexNone.png", ), ( "/foo/bar/myslide2.svs", "/foo/bar/myslide/processed", (345, 111, 123, 123), "/foo/bar/myslide/processed/myslide2-22x-345x111-123x123.png", ), ( "/foo/bar/myslide2.svs", "/foo/bar/myslide/processed", (None, None, None, None), "/foo/bar/myslide/processed/myslide2*.png", ), ]) def scaled_img_path_fixture(self, request): slide_path, proc_path, slide_dims, expected_value = request.param return slide_path, proc_path, slide_dims, expected_value @pytest.fixture(params=[ ( (245, 123, 145, 99), "/foo/bar/b/0/9", "/foo/bar/myslide.svs", "processed", 64, "/foo/bar/b/0/9/myslide-64x-245x123-145x99.png", ), ( (245, 123, 145, 99), "/foo/bar/b/0/9", "/foo/bar/myslide.svs", "processed", 32, "/foo/bar/b/0/9/myslide-32x-245x123-145x99.png", ), ( (None, None, None, None), "/foo/bar/b/0/9", "/foo/bar/myslide.svs", "processed", 64, "/foo/bar/b/0/9/myslide*.png", ), ( (None, 234, 192, None), "/foo/bar/b/0/9", "/foo/bar/myslide.svs", "processed", 64, "/foo/bar/b/0/9/myslide-64x-Nonex234-192xNone.png", ), ( (123, 234, 192, None), "/foo/bar/b/0/9", "/foo/bar/myslide.svs", "processed", 64, "/foo/bar/b/0/9/myslide-64x-123x234-192xNone.png", ), ( (None, None, 192, None), "/foo/bar/b/0/9", "/foo/bar/myslide.svs", "processed", 64, "/foo/bar/b/0/9/myslide-64x-NonexNone-192xNone.png", ), ]) def breadcumb_fixture(self, request): ( resampled_dims, dir_path, slide_path, proc_path, scale_factor, expected_path, ) = request.param return ( resampled_dims, dir_path, slide_path, proc_path, scale_factor, expected_path, ) @pytest.fixture(params=[("/foo/bar/myslide.svs", "myslide"), ("/foo/myslide.svs", "myslide")]) def slide_name_fixture(self, request): slide_path, expceted_value = request.param return slide_path, expceted_value @pytest.fixture(params=[ (CoordinatePair(0, 128, 0, 128), True), (CoordinatePair(800000, 90000, 8000010, 90010), False), (CoordinatePair(800000, 90000, -1, 90010), False), ]) def valid_coords_fixture(self, request): coords, expected_result = request.param return coords, expected_result # fixture components --------------------------------------------- @pytest.fixture def resampled_dims_(self, request): return method_mock(request, Slide, "_resampled_dimensions") @pytest.fixture def dimensions_(self, request): return property_mock(request, Slide, "dimensions") @pytest.fixture def RgbToGrayscale_(self, request): return class_mock(request, "histolab.filters.image_filters.RgbToGrayscale") @pytest.fixture def OtsuThreshold_(self, request): return class_mock(request, "histolab.filters.image_filters.OtsuThreshold") @pytest.fixture def BinaryDilation_(self, request): return class_mock( request, "histolab.filters.morphological_filters.BinaryDilation") @pytest.fixture def RemoveSmallHoles_(self, request): return class_mock( request, "histolab.filters.morphological_filters.RemoveSmallHoles") @pytest.fixture def RemoveSmallObjects_(self, request): return class_mock( request, "histolab.filters.morphological_filters.RemoveSmallObjects")
class Describe_GridTiler(object): def it_constructs_from_args(self, request): _init = initializer_mock(request, GridTiler) grid_tiler = GridTiler( (512, 512), 2, True, 0, "", ".png", ) _init.assert_called_once_with(ANY, (512, 512), 2, True, 0, "", ".png") assert isinstance(grid_tiler, GridTiler) assert isinstance(grid_tiler, Tiler) def but_it_has_wrong_tile_size_value(self, request): with pytest.raises(ValueError) as err: GridTiler((512, -1)) assert isinstance(err.value, ValueError) assert str(err.value) == "Tile size must be greater than 0 ((512, -1))" def or_it_has_not_available_level_value(self, request, tmpdir): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_50X50_RGB_RANDOM_COLOR image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") grid_tiler = GridTiler((512, 512), 3) with pytest.raises(ValueError) as err: grid_tiler.extract(slide) assert isinstance(err.value, ValueError) assert str(err.value ) == "Level 3 not available. Number of available levels: 1" def or_it_has_negative_level_value(self, request): with pytest.raises(ValueError) as err: GridTiler((512, 512), -1) assert isinstance(err.value, ValueError) assert str(err.value) == "Level cannot be negative (-1)" @pytest.mark.parametrize("tile_size", ((512, 512), (128, 128), (10, 10))) def it_knows_its_tile_size(self, request, tile_size): grid_tiler = GridTiler(tile_size, 10, 0) tile_size_ = grid_tiler.tile_size assert type(tile_size_) == tuple assert tile_size_ == tile_size def it_knows_its_tile_filename(self, request, tile_filename_fixture): ( tile_size, level, check_tissue, pixel_overlap, prefix, suffix, tile_coords, tiles_counter, expected_filename, ) = tile_filename_fixture grid_tiler = GridTiler(tile_size, level, check_tissue, pixel_overlap, prefix, suffix) _filename = grid_tiler._tile_filename(tile_coords, tiles_counter) assert type(_filename) == str assert _filename == expected_filename @pytest.mark.parametrize( "check_tissue, expected_box", ( (False, SparseArrayMock.ONES_500X500_BOOL), (True, SparseArrayMock.RANDOM_500X500_BOOL), ), ) def it_knows_its_box_mask(self, request, tmpdir, check_tissue, expected_box): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") _biggest_tissue_box_mask = property_mock(request, Slide, "biggest_tissue_box_mask") _biggest_tissue_box_mask.return_value = expected_box grid_tiler = GridTiler((128, 128), 0, check_tissue=check_tissue) box_mask = grid_tiler.box_mask(slide) _biggest_tissue_box_mask.assert_called_once_with() assert type(box_mask) == sparse._coo.core.COO np.testing.assert_array_almost_equal(box_mask.todense(), expected_box.todense()) @pytest.mark.parametrize( "bbox_coordinates, pixel_overlap, expected_n_tiles_row", ( (CoordinatePair(x_ul=0, y_ul=0, x_br=6060, y_br=1917), 0, 11), (CoordinatePair(x_ul=0, y_ul=0, x_br=1921, y_br=2187), 0, 3), (CoordinatePair(x_ul=0, y_ul=0, x_br=1921, y_br=2187), 128, 5), (CoordinatePair(x_ul=0, y_ul=0, x_br=1921, y_br=2187), -128, 3), ), ) def it_can_calculate_n_tiles_row(self, request, bbox_coordinates, pixel_overlap, expected_n_tiles_row): grid_tiler = GridTiler((512, 512), 2, True, pixel_overlap) n_tiles_row = grid_tiler._n_tiles_row(bbox_coordinates) assert type(n_tiles_row) == int assert n_tiles_row == expected_n_tiles_row @pytest.mark.parametrize( "bbox_coordinates, pixel_overlap, expected_n_tiles_column", ( (CoordinatePair(x_ul=0, y_ul=0, x_br=6060, y_br=1917), 0, 3), (CoordinatePair(x_ul=0, y_ul=0, x_br=6060, y_br=1917), -1, 3), (CoordinatePair(x_ul=0, y_ul=0, x_br=1921, y_br=2187), 0, 4), (CoordinatePair(x_ul=0, y_ul=0, x_br=1921, y_br=2187), 128, 5), ), ) def it_can_calculate_n_tiles_column(self, request, bbox_coordinates, pixel_overlap, expected_n_tiles_column): grid_tiler = GridTiler((512, 512), 2, True, pixel_overlap) n_tiles_column = grid_tiler._n_tiles_column(bbox_coordinates) assert type(n_tiles_column) == int assert n_tiles_column == expected_n_tiles_column def it_can_generate_grid_tiles( self, request, tmpdir, grid_tiles_fixture, ): ( coords1, coords2, check_tissue, has_enough_tissue, expected_n_tiles, ) = grid_tiles_fixture tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") _extract_tile = method_mock(request, Slide, "extract_tile") _has_enough_tissue = method_mock(request, Tile, "has_enough_tissue") _has_enough_tissue.side_effect = has_enough_tissue _grid_coordinates_generator = method_mock( request, GridTiler, "_grid_coordinates_generator") _grid_coordinates_generator.return_value = [coords1, coords2] tile1 = Tile(image, coords1) tile2 = Tile(image, coords2) _extract_tile.side_effect = [tile1, tile2] grid_tiler = GridTiler((10, 10), level=0, check_tissue=check_tissue) generated_tiles = list(grid_tiler._grid_tiles_generator(slide)) _grid_coordinates_generator.assert_called_once_with(grid_tiler, slide) _extract_tile.call_args_list == ([call(coords1, 0), call(coords2, 0)]) assert len(generated_tiles) == expected_n_tiles if expected_n_tiles == 2: assert generated_tiles == [(tile1, coords1), (tile2, coords2)] if expected_n_tiles == 1: assert generated_tiles == [(tile1, coords1)] if expected_n_tiles == 0: assert generated_tiles == [] def and_doesnt_raise_error_with_wrong_coordinates(self, request, tmpdir): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") coords = CoordinatePair(5800, 6000, 5800, 6000) _grid_coordinates_generator = method_mock( request, GridTiler, "_grid_coordinates_generator") _grid_coordinates_generator.return_value = [coords] grid_tiler = GridTiler((10, 10)) generated_tiles = list(grid_tiler._grid_tiles_generator(slide)) assert len(generated_tiles) == 0 _grid_coordinates_generator.assert_called_once_with(grid_tiler, slide) # fixtures ------------------------------------------------------- @pytest.fixture(params=( ( (512, 512), 3, True, 0, "", ".png", CoordinatePair(0, 512, 0, 512), 3, "tile_3_level3_0-512-0-512.png", ), ( (512, 512), 0, True, 0, "folder/", ".png", CoordinatePair(4, 127, 4, 127), 10, "folder/tile_10_level0_4-127-4-127.png", ), )) def tile_filename_fixture(self, request): ( tile_size, level, check_tissue, pixel_overlap, prefix, suffix, tile_coords, tiles_counter, expected_filename, ) = request.param return ( tile_size, level, check_tissue, pixel_overlap, prefix, suffix, tile_coords, tiles_counter, expected_filename, ) @pytest.fixture(params=( ( CoordinatePair(0, 10, 0, 10), CoordinatePair(0, 10, 0, 10), True, [True, True], 2, ), ( CoordinatePair(0, 10, 0, 10), CoordinatePair(0, 10, 0, 10), False, [True, True], 2, ), ( CoordinatePair(0, 10, 0, 10), CoordinatePair(0, 10, 0, 10), False, [False, False], 2, ), ( CoordinatePair(0, 10, 0, 10), CoordinatePair(0, 10, 0, 10), True, [False, False], 0, ), ( CoordinatePair(0, 10, 0, 10), CoordinatePair(0, 10, 0, 10), True, [True, False], 1, ), )) def grid_tiles_fixture(self, request): ( coords1, coords2, check_tissue, has_enough_tissue, expected_n_tiles, ) = request.param return ( coords1, coords2, check_tissue, has_enough_tissue, expected_n_tiles, )
class Describe_RandomTiler(object): def it_constructs_from_args(self, request): _init = initializer_mock(request, RandomTiler) random_tiler = RandomTiler((512, 512), 10, 2, 7, True, "", ".png", 1e4) _init.assert_called_once_with(ANY, (512, 512), 10, 2, 7, True, "", ".png", 1e4) assert isinstance(random_tiler, RandomTiler) assert isinstance(random_tiler, Tiler) def but_it_has_wrong_tile_size_value(self, request): with pytest.raises(ValueError) as err: RandomTiler((512, -1), 10, 0) assert isinstance(err.value, ValueError) assert str(err.value) == "Tile size must be greater than 0 ((512, -1))" def or_it_has_not_available_level_value(self, request, tmpdir): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_50X50_RGB_RANDOM_COLOR image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") random_tiler = RandomTiler((512, 512), 10, 3) with pytest.raises(ValueError) as err: random_tiler.extract(slide) assert isinstance(err.value, ValueError) assert str(err.value ) == "Level 3 not available. Number of available levels: 1" def or_it_has_negative_level_value(self, request): with pytest.raises(ValueError) as err: RandomTiler((512, 512), 10, -1) assert isinstance(err.value, ValueError) assert str(err.value) == "Level cannot be negative (-1)" def or_it_has_wrong_max_iter(self, request): with pytest.raises(ValueError) as err: RandomTiler((512, 512), 10, 0, max_iter=3) assert isinstance(err.value, ValueError) assert ( str(err.value) == "The maximum number of iterations (3) must be grater than or equal to the maximum number of tiles (10)." ) def or_it_has_wrong_seed(self, request, tmpdir): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_50X50_RGB_RANDOM_COLOR image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") random_tiler = RandomTiler((512, 512), 10, 0, seed=-1) with pytest.raises(ValueError) as err: random_tiler.extract(slide) assert isinstance(err.value, ValueError) assert str(err.value) == "Seed must be between 0 and 2**32 - 1" @pytest.mark.parametrize("tile_size", ((512, 512), (128, 128), (10, 10))) def it_knows_its_tile_size(self, request, tile_size): random_tiler = RandomTiler(tile_size, 10, 0) tile_size_ = random_tiler.tile_size assert type(tile_size_) == tuple assert tile_size_ == tile_size @pytest.mark.parametrize("max_iter", (1000, 10, 3000)) def it_knows_its_max_iter(self, request, max_iter): random_tiler = RandomTiler((128, 128), 10, 0, max_iter=max_iter) max_iter_ = random_tiler.max_iter assert type(max_iter_) == int assert max_iter_ == max_iter def it_knows_its_tile_filename(self, request, tile_filename_fixture): ( tile_size, n_tiles, level, seed, check_tissue, prefix, suffix, tile_coords, tiles_counter, expected_filename, ) = tile_filename_fixture random_tiler = RandomTiler(tile_size, n_tiles, level, seed, check_tissue, prefix, suffix) _filename = random_tiler._tile_filename(tile_coords, tiles_counter) assert type(_filename) == str assert _filename == expected_filename def it_can_generate_random_coordinates(self, request, tmpdir): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") _box_mask_lvl = method_mock(request, RandomTiler, "box_mask_lvl") _box_mask_lvl.return_value = SparseArrayMock.ONES_500X500_BOOL _tile_size = property_mock(request, RandomTiler, "tile_size") _tile_size.return_value = (128, 128) _np_random_choice1 = function_mock(request, "numpy.random.choice") _np_random_choice1.return_value = 0 _np_random_choice2 = function_mock(request, "numpy.random.choice") _np_random_choice2.return_value = 0 _scale_coordinates = function_mock(request, "histolab.tiler.scale_coordinates") random_tiler = RandomTiler((128, 128), 10, 0) random_tiler._random_tile_coordinates(slide) _box_mask_lvl.assert_called_once_with(random_tiler, slide) _tile_size.assert_has_calls([call((128, 128))]) _scale_coordinates.assert_called_once_with( reference_coords=CoordinatePair(x_ul=0, y_ul=0, x_br=128, y_br=128), reference_size=(500, 500), target_size=(500, 500), ) @pytest.mark.parametrize( "check_tissue, expected_box", ( (False, SparseArrayMock.RANDOM_500X500_BOOL), (True, SparseArrayMock.RANDOM_500X500_BOOL), ), ) def it_knows_its_box_mask(self, request, tmpdir, check_tissue, expected_box): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") _biggest_tissue_box_mask = property_mock(request, Slide, "biggest_tissue_box_mask") _biggest_tissue_box_mask.return_value = expected_box random_tiler = RandomTiler((128, 128), 10, 0, check_tissue=check_tissue) box_mask = random_tiler.box_mask(slide) _biggest_tissue_box_mask.assert_called_once_with() assert type(box_mask) == sparse._coo.core.COO np.testing.assert_array_almost_equal(box_mask.todense(), expected_box.todense()) @pytest.mark.parametrize( "coords1, coords2, check_tissue, has_enough_tissue, max_iter, expected_n_tiles", ( ( CoordinatePair(0, 10, 0, 10), CoordinatePair(0, 10, 0, 10), True, [True, True], 10, 2, ), ( CoordinatePair(0, 10, 0, 10), CoordinatePair(0, 10, 0, 10), True, [True, False], 2, 1, ), ( CoordinatePair(0, 10, 0, 10), CoordinatePair(5900, 6000, 5900, 6000), # wrong coordinates True, [True, True], 2, 2, ), ( CoordinatePair(0, 10, 0, 10), CoordinatePair(0, 10, 0, 10), False, [True, True], 10, 2, ), ( CoordinatePair(0, 10, 0, 10), CoordinatePair(0, 10, 0, 10), False, [False, False], 10, 2, ), ( CoordinatePair(0, 10, 0, 10), CoordinatePair(0, 10, 0, 10), True, [False, False], 10, 0, ), ), ) def it_can_generate_random_tiles( self, request, tmpdir, coords1, coords2, check_tissue, has_enough_tissue, max_iter, expected_n_tiles, ): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") _extract_tile = method_mock(request, Slide, "extract_tile") _has_enough_tissue = method_mock(request, Tile, "has_enough_tissue") _has_enough_tissue.side_effect = has_enough_tissue * (max_iter // 2) _random_tile_coordinates = method_mock(request, RandomTiler, "_random_tile_coordinates") _random_tile_coordinates.side_effect = [coords1, coords2 ] * (max_iter // 2) tile1 = Tile(image, coords1) tile2 = Tile(image, coords2) _extract_tile.side_effect = [tile1, tile2] * (max_iter // 2) random_tiler = RandomTiler((10, 10), 2, level=0, max_iter=max_iter, check_tissue=check_tissue) generated_tiles = list(random_tiler._random_tiles_generator(slide)) _random_tile_coordinates.assert_called_with(random_tiler, slide) assert _random_tile_coordinates.call_count <= random_tiler.max_iter _extract_tile.call_args_list == ([call(coords1, 0), call(coords2, 0)]) assert len(generated_tiles) == expected_n_tiles if expected_n_tiles == 2: assert generated_tiles == [(tile1, coords1), (tile2, coords2)] @pytest.mark.parametrize( "level, box_mask, expected_box_mask_lvl", ( ( 0, SparseArrayMock.RANDOM_500X500_BOOL, SparseArrayMock.RANDOM_500X500_BOOL, ), # TODO: use image with more than 1 level ), ) def it_knows_its_box_mask_lvl(self, request, tmpdir, level, box_mask, expected_box_mask_lvl): tmp_path_ = tmpdir.mkdir("myslide") image = PILImageMock.DIMS_500X500_RGBA_COLOR_155_249_240 image.save(os.path.join(tmp_path_, "mywsi.png"), "PNG") slide_path = os.path.join(tmp_path_, "mywsi.png") slide = Slide(slide_path, "processed") _box_mask = method_mock(request, RandomTiler, "box_mask") _box_mask.return_value = box_mask random_tiler = RandomTiler((128, 128), 10, level) box_mask_lvl = random_tiler.box_mask_lvl(slide) assert type(box_mask_lvl) == sparse._coo.core.COO np.testing.assert_array_almost_equal(box_mask_lvl.todense(), expected_box_mask_lvl.todense()) # fixtures ------------------------------------------------------- @pytest.fixture(params=( ( (512, 512), 10, 3, 7, True, "", ".png", CoordinatePair(0, 512, 0, 512), 3, "tile_3_level3_0-512-0-512.png", ), ( (512, 512), 10, 0, 7, True, "folder/", ".png", CoordinatePair(4, 127, 4, 127), 10, "folder/tile_10_level0_4-127-4-127.png", ), )) def tile_filename_fixture(self, request): ( tile_size, n_tiles, level, seed, check_tissue, prefix, suffix, tile_coords, tiles_counter, expected_filename, ) = request.param return ( tile_size, n_tiles, level, seed, check_tissue, prefix, suffix, tile_coords, tiles_counter, expected_filename, )
def test_scale_coordinates(ref_coords, ref_size, target_size, expected_value): x_ul, y_ul, x_br, y_br = expected_value scaled_coords = scale_coordinates(ref_coords, ref_size, target_size) assert scaled_coords == CoordinatePair(x_ul, y_ul, x_br, y_br)
def test_region_coordinates(): region = Region(index=0, area=14, bbox=(0, 1, 1, 2), center=(0.5, 0.5)) region_coords_ = region_coordinates(region) assert region_coords_ == CoordinatePair(x_ul=1, y_ul=0, x_br=2, y_br=1)
lazyproperty, np_to_pil, polygon_to_mask_array, region_coordinates, resize_mask, scale_coordinates, threshold_to_mask, ) from ..util import load_expectation @pytest.mark.parametrize( "ref_coords, ref_size, target_size, expected_value", ( (CoordinatePair(0, 2, 4, 5), (10, 10), (5, 5), (0, 1, 2, 2)), (CoordinatePair(90, 112, 124, 125), (100, 100), (95, 95), (85, 106, 117, 118)), ), ) def test_scale_coordinates(ref_coords, ref_size, target_size, expected_value): x_ul, y_ul, x_br, y_br = expected_value scaled_coords = scale_coordinates(ref_coords, ref_size, target_size) assert scaled_coords == CoordinatePair(x_ul, y_ul, x_br, y_br) @pytest.mark.parametrize( "img_array, expected_mode, expected_size, expected_type, expected_array", (