def it_can_generate_grid_tiles_with_no_check_tissue( self, request, tmpdir, tile1, tile2, has_enough_tissue, expected_n_tiles, ): slide, _ = base_test_slide(tmpdir, PILIMG.RGBA_COLOR_500X500_155_249_240) _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 = [ CP(0, 10, 0, 10), CP(0, 10, 0, 10) ] _extract_tile.side_effect = [tile1, tile2] grid_tiler = GridTiler((10, 10), level=0, check_tissue=False) tiles = [tile1, tile2] generated_tiles = list(grid_tiler._tiles_generator(slide)) _grid_coordinates_generator.assert_called_once_with(grid_tiler, slide) assert _extract_tile.call_args_list == ([ call(slide, CP(0, 10, 0, 10), 0), call(slide, CP(0, 10, 0, 10), 0) ]) _has_enough_tissue.assert_not_called() assert len(generated_tiles) == expected_n_tiles for i, tile in enumerate(generated_tiles): assert tile[0] == tiles[i]
def it_locates_tiles_on_the_slide( self, request, fixture_slide, binary_mask, tile_size, level, check_tissue, expectation, ): slide = Slide(fixture_slide, "") grid_tiles_extractor = GridTiler( tile_size=tile_size, level=level, check_tissue=check_tissue, ) expected_img = load_expectation(expectation, type_="png") tiles_location_img = grid_tiles_extractor.locate_tiles(slide, binary_mask, scale_factor=10) # --- Expanding test report with actual and expected images --- expand_tests_report(request, expected=expected_img, actual=tiles_location_img) np.testing.assert_array_almost_equal(tiles_location_img, expected_img)
def it_can_extract_grid_tiles(self, request, tmpdir): tmp_path_ = tmpdir.mkdir("myslide") image = PILIMG.RGBA_COLOR_500X500_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")) _tiles_generator = method_mock(request, GridTiler, "_tiles_generator") coords = CP(0, 10, 0, 10) tile = Tile(image, coords) _tiles_generator.return_value = [(tile, coords), (tile, coords)] _tile_filename = method_mock(request, GridTiler, "_tile_filename") _tile_filename.side_effect = [ os.path.join(tmp_path_, "processed", "tiles", f"tile_{i}_level2_0-10-0-10.png") for i in range(2) ] _has_valid_tile_size = method_mock(request, GridTiler, "_has_valid_tile_size") _has_valid_tile_size.return_value = True grid_tiler = GridTiler((10, 10), level=0) grid_tiler.extract(slide) assert _tile_filename.call_args_list == [ call(grid_tiler, coords, 0), call(grid_tiler, coords, 1), ] assert os.path.exists( os.path.join(tmp_path_, "processed", "tiles", "tile_0_level2_0-10-0-10.png")) assert os.path.exists( os.path.join(tmp_path_, "processed", "tiles", "tile_1_level2_0-10-0-10.png")) _has_valid_tile_size.assert_called_once_with(grid_tiler, slide)
def it_can_generate_grid_tiles( self, request, tmpdir, tile1, tile2, check_tissue, has_enough_tissue, expected_n_tiles, ): tmp_path_ = tmpdir.mkdir("myslide") image = PILIMG.RGBA_COLOR_500X500_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 = [CP(0, 10, 0, 10), CP(0, 10, 0, 10)] _extract_tile.side_effect = [tile1, tile2] grid_tiler = GridTiler((10, 10), level=0, check_tissue=check_tissue) tiles = [tile1, tile2] generated_tiles = list(grid_tiler._tiles_generator(slide)) _grid_coordinates_generator.assert_called_once_with(grid_tiler, slide) assert _extract_tile.call_args_list == ( [call(slide, CP(0, 10, 0, 10), 0), call(slide, CP(0, 10, 0, 10), 0)] ) assert len(generated_tiles) == expected_n_tiles for i, tile in enumerate(generated_tiles): assert tile[0] == tiles[i]
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
def it_can_calculate_n_tiles_column(self, bbox_coordinates, pixel_overlap, expected_n_tiles_column): grid_tiler = GridTiler((512, 512), 2, True, 80, 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_knows_if_it_has_valid_tile_size(self, tmpdir, tile_size, expected_result): slide, _ = base_test_slide(tmpdir, PILIMG.RGBA_COLOR_500X500_155_249_240) grid_tiler = GridTiler(tile_size, 0, True) result = grid_tiler._has_valid_tile_size(slide) assert type(result) == bool assert result == expected_result
def or_it_has_not_available_level_value(self, tmpdir): slide, _ = base_test_slide(tmpdir, PILIMG.RGB_RANDOM_COLOR_500X500) binary_mask = BiggestTissueBoxMask() grid_tiler = GridTiler((128, 128), 3) with pytest.raises(LevelError) as err: grid_tiler.extract(slide, binary_mask) assert isinstance(err.value, LevelError) assert str(err.value ) == "Level 3 not available. Number of available levels: 1"
def it_knows_whether_coordinates_are_within_extraction_mask( self, tile_coords, expected_result): grid_tiler = GridTiler((2, 2), level=0) # tile size doens't matter here mask = COMPLEX_MASK4 coords_within_extraction_mask = ( grid_tiler._are_coordinates_within_extraction_mask( tile_coords, mask)) assert type(coords_within_extraction_mask) == bool assert coords_within_extraction_mask == expected_result
def and_doesnt_raise_error_with_wrong_coordinates(self, request, tmpdir): slide, _ = base_test_slide(tmpdir, PILIMG.RGBA_COLOR_500X500_155_249_240) coords = CP(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._tiles_generator(slide)) assert len(generated_tiles) == 0 _grid_coordinates_generator.assert_called_once_with(grid_tiler, slide)
def or_it_has_not_available_level_value(self, tmpdir): tmp_path_ = tmpdir.mkdir("myslide") image = PILIMG.RGB_RANDOM_COLOR_500X500 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((128, 128), 3) with pytest.raises(LevelError) as err: grid_tiler.extract(slide) assert isinstance(err.value, LevelError) assert str(err.value) == "Level 3 not available. Number of available levels: 1"
def it_knows_its_box_mask(self, request, tmpdir, check_tissue, expected_box): slide, _ = base_test_slide(tmpdir, PILIMG.RGBA_COLOR_500X500_155_249_240) _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) == np.ndarray np.testing.assert_array_almost_equal(box_mask, expected_box)
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)
def it_knows_its_tile_filename( self, level, pixel_overlap, prefix, tile_coords, tiles_counter, expected_filename, ): grid_tiler = GridTiler((512, 512), level, True, pixel_overlap, prefix, ".png") _filename = grid_tiler._tile_filename(tile_coords, tiles_counter) assert type(_filename) == str assert _filename == expected_filename
def it_knows_its_tile_size(self, tile_size): grid_tiler = GridTiler(tile_size, 10, True, 0) tile_size_ = grid_tiler.tile_size assert type(tile_size_) == tuple assert tile_size_ == tile_size
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 it_knows_its_box_mask(self, request, tmpdir, check_tissue, expected_box): tmp_path_ = tmpdir.mkdir("myslide") image = PILIMG.RGBA_COLOR_500X500_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) == np.ndarray np.testing.assert_array_almost_equal(box_mask, expected_box)
def it_locates_tiles_on_the_slide(self, request, fixture_slide, expectation, tmpdir): slide = Slide(fixture_slide, os.path.join(tmpdir, "processed")) grid_tiles_extractor = GridTiler( tile_size=(512, 512), level=0, check_tissue=False, ) expected_img = load_expectation(expectation, type_="png") tiles_location_img = grid_tiles_extractor.locate_tiles(slide, scale_factor=10) # --- Expanding test report with actual and expected images --- expand_tests_report(request, expected=expected_img, actual=tiles_location_img) np.testing.assert_array_almost_equal(np.asarray(tiles_location_img), expected_img)
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
def but_it_raises_tilesizeerror_if_tilesize_larger_than_slidesize( self, request, tmpdir, image, size): tmp_path_ = tmpdir.mkdir("myslide") 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")) _has_valid_tile_size = method_mock(request, GridTiler, "_has_valid_tile_size") _has_valid_tile_size.return_value = False grid_tiler = GridTiler((50, 52), level=0) with pytest.raises(TileSizeError) as err: grid_tiler.extract(slide) assert isinstance(err.value, TileSizeError) assert (str( err.value ) == f"Tile size (50, 52) is larger than slide size {size} at level 0") _has_valid_tile_size.assert_called_once_with(grid_tiler, slide)
def but_with_wrong_coordinates(self, request, tmpdir): slide, _ = base_test_slide(tmpdir, PILIMG.RGBA_COLOR_500X500_155_249_240) _has_enough_tissue = method_mock(request, Tile, "has_enough_tissue") _has_enough_tissue.return_value = False _grid_coordinates_generator = method_mock( request, GridTiler, "_grid_coordinates_generator") coords1 = CP(600, 610, 600, 610) coords2 = CP(0, 10, 0, 10) _grid_coordinates_generator.return_value = [coords1, coords2] grid_tiler = GridTiler((10, 10), level=0, check_tissue=False) generated_tiles = list(grid_tiler._tiles_generator(slide)) _grid_coordinates_generator.assert_called_once_with(grid_tiler, slide) assert len(generated_tiles) == 1 # generated_tiles[0][0] is a Tile object but we don't know what object it is # because Slide.extract_tile is not mocked (for the exception to happen inside) assert isinstance(generated_tiles[0][0], Tile) assert generated_tiles[0][1] == coords2
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 but_with_wrong_coordinates(self, request, tmpdir): tmp_path_ = tmpdir.mkdir("myslide") image = PILIMG.RGBA_COLOR_500X500_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") _has_enough_tissue = method_mock(request, Tile, "has_enough_tissue") _has_enough_tissue.return_value = False _grid_coordinates_generator = method_mock( request, GridTiler, "_grid_coordinates_generator" ) coords1 = CP(600, 610, 600, 610) coords2 = CP(0, 10, 0, 10) _grid_coordinates_generator.return_value = [coords1, coords2] grid_tiler = GridTiler((10, 10), level=0, check_tissue=False) generated_tiles = list(grid_tiler._tiles_generator(slide)) _grid_coordinates_generator.assert_called_once_with(grid_tiler, slide) assert len(generated_tiles) == 1 # generated_tiles[0][0] is a Tile object but we don't know what object it is # because Slide.extract_tile is not mocked (for the exception to happen inside) assert isinstance(generated_tiles[0][0], Tile) assert generated_tiles[0][1] == coords2
from histolab.tiler import GridTiler from histolab.slide import Slide prostate_svs, prostate_path = prostate_tissue() #TODO CHANGE PATHS HERE! SVS_PATH = prostate_path N_TILES = 175 TILE_SIZE = 512 EXTRACTION_LVL = 1 # %% slide_obj= Slide(str(SVS_PATH), processed_path=".", slide_filters="texture") slide_obj # %% tiler = GridTiler( tile_size= (TILE_SIZE, TILE_SIZE), level = EXTRACTION_LVL, check_tissue=True, pixel_overlap= 0, prefix= str(Path(__file__).parent / slide_obj.name), suffix=".png", partial=1.0, maximum=N_TILES, ) # %% # create and save the extraction map as png plot = tiler.extraction_plot(slide_obj) Image.fromarray(plot).save('tile_extraction_example.png') # %%
def but_it_has_wrong_tile_size_value(self): 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_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)"
""" https://histolab.readthedocs.io/en/latest/api/tiler.html Extract tiles arranged in a grid and save them to disk, following this filename pattern: {prefix}tile_{tiles_counter}_level{level}_{x_ul_wsi}-{y_ul_wsi}-{x_br_wsi}-{y_br_wsi}{suffix} """ tile_sz = 300 level = 0 # check_tissue = True check_tissue = False grid_tiler = GridTiler( tile_size=(tile_sz, tile_sz), # (width, height) of the extracted tiles level=level, # Level from which extract the tiles. Default is 0. check_tissue= check_tissue, # Whether to check if the tile has enough tissue to be saved. Default is True. pixel_overlap= 0, # Number of overlapping pixels (for both height and width) between two adjacent tiles. prefix= '', # Prefix to be added to the tile filename. Default is an empty string. suffix=".png" # default ) # Find the smallest slide idx = meta_df['openslide.level[0].height'].isin( [meta_df['openslide.level[0].height'].min()]) img_name = meta_df.loc[idx, 'aperio.ImageID'].values[0] fname = imgpath / f'{img_name}.svs' # Slide instance # path: path to WSI file # processed_path: path to save thumbnails and scaled images