def it_can_save_report(self, request, tmpdir): tmp_path_ = tmpdir.mkdir("path") coords = CP(0, 10, 0, 10) highest_score_tiles = [(0.8, coords), (0.7, coords)] highest_scaled_score_tiles = [(0.1, coords), (0.0, coords)] filenames = ["tile0.png", "tile1.png"] random_scorer_ = instance_mock(request, RandomScorer) score_tiler = ScoreTiler(random_scorer_, (10, 10), 2, 2) report_ = [ "filename,score,scaled_score", "tile0.png,0.8,0.1", "tile1.png,0.7,0.0", ] score_tiler._save_report( os.path.join(tmp_path_, "report.csv"), highest_score_tiles, highest_scaled_score_tiles, filenames, ) assert os.path.exists(os.path.join(tmp_path_, "report.csv")) with open(os.path.join(tmp_path_, "report.csv"), newline="") as f: reader = csv.reader(f) report = [",".join(row) for row in reader] assert report == report_
def it_can_calculate_scores(self, request): slide = instance_mock(request, Slide) coords = CP(0, 10, 0, 10) image = PILIMG.RGB_RANDOM_COLOR_500X500 tile = Tile(image, coords) _tiles_generator = method_mock(request, GridTiler, "_tiles_generator") # it needs to be a generator _tiles_generator.return_value = ((tile, coords) for i in range(3)) _scorer = instance_mock(request, RandomScorer) _scorer.side_effect = [0.5, 0.7] score_tiler = ScoreTiler(_scorer, (10, 10), 2, 0) binary_mask = BiggestTissueBoxMask() scores = score_tiler._scores(slide, binary_mask) assert _tiles_generator.call_args_list == [ call(score_tiler, slide, binary_mask), call(score_tiler, slide, binary_mask), ] assert _scorer.call_args_list == [call(tile), call(tile)] assert type(scores) == list assert type(scores[0]) == tuple assert type(scores[0][0]) == float assert type(scores[0][1]) == CP assert scores == [(0.5, coords), (0.7, coords)]
def but_it_raises_error_with_negative_n_tiles_value(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")) _scores = method_mock(request, ScoreTiler, "_scores") coords = CP(0, 10, 0, 10) _scores.return_value = [ (0.7, coords), (0.5, coords), (0.2, coords), (0.8, coords), (0.1, coords), ] _scorer = instance_mock(request, RandomScorer) score_tiler = ScoreTiler(_scorer, (10, 10), -1, 0) binary_mask = BiggestTissueBoxMask() with pytest.raises(ValueError) as err: score_tiler.extract(slide, binary_mask) assert isinstance(err.value, ValueError) assert str(err.value) == "'n_tiles' cannot be negative (-1)" _scores.assert_called_once_with(score_tiler, slide, binary_mask)
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) score_tiler = ScoreTiler(RandomScorer(), tile_size, 2, 0) result = score_tiler._has_valid_tile_size(slide) assert type(result) == bool assert result == expected_result
def or_it_raises_levelerror_if_has_not_available_level_value(self, tmpdir): slide, _ = base_test_slide(tmpdir, PILIMG.RGB_RANDOM_COLOR_500X500) score_tiler = ScoreTiler(None, (10, 10), 2, 3) with pytest.raises(LevelError) as err: score_tiler.extract(slide) assert isinstance(err.value, LevelError) assert str(err.value ) == "Level 3 not available. Number of available levels: 1"
def it_can_extract_score_tiles_and_save_report(self, request, tmpdir): _extract_tile = method_mock(request, Slide, "extract_tile") 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")) coords = CP(0, 10, 0, 10) _tiles_generator = method_mock( request, ScoreTiler, "_tiles_generator", ) tile = Tile(image, coords) _extract_tile.return_value = tile _tiles_generator.return_value = ( [(0.8, coords), (0.7, coords)], [(0.8, coords), (0.7, coords)], ) _tile_filename = method_mock(request, GridTiler, "_tile_filename") _tile_filename.side_effect = [ f"tile_{i}_level2_0-10-0-10.png" for i in range(2) ] _save_report = method_mock(request, ScoreTiler, "_save_report", autospec=False) random_scorer = RandomScorer() score_tiler = ScoreTiler(random_scorer, (10, 10), 2, 0) binary_mask = BiggestTissueBoxMask() score_tiler.extract(slide, binary_mask, "report.csv") assert _extract_tile.call_args_list == [ call(slide, coords, 0, (10, 10)), call(slide, coords, 0, (10, 10)), ] _tiles_generator.assert_called_with(score_tiler, slide, binary_mask) assert _tile_filename.call_args_list == [ call(score_tiler, coords, 0), call(score_tiler, coords, 1), ] assert os.path.exists( os.path.join(tmp_path_, "processed", "tile_0_level2_0-10-0-10.png")) assert os.path.exists( os.path.join(tmp_path_, "processed", "tile_1_level2_0-10-0-10.png")) _save_report.assert_called_once_with( "report.csv", [(0.8, coords), (0.7, coords)], [(0.8, coords), (0.7, coords)], [f"tile_{i}_level2_0-10-0-10.png" for i in range(2)], )
def it_can_scale_scores(self): coords = [CP(0, 10 * i, 0, 10) for i in range(3)] scores = [0.3, 0.4, 0.7] scores_ = list(zip(scores, coords)) score_tiler = ScoreTiler(None, (10, 10), 2, 0) expected_scaled_coords = list(zip([0.0, 0.2500000000000001, 1.0], coords)) scaled_scores = score_tiler._scale_scores(scores_) for (score, coords_), (expected_score, expected_coords) in zip( scaled_scores, expected_scaled_coords ): assert round(score, 5) == round(expected_score, 5) assert coords_ == expected_coords
def but_it_raises_runtimeerror_if_no_tiles_are_extracted(self, request): slide = instance_mock(request, Slide) _tiles_generator = method_mock(request, GridTiler, "_tiles_generator") # it needs to be an empty generator _tiles_generator.return_value = (n for n in []) score_tiler = ScoreTiler(None, (10, 10), 2, 0) with pytest.raises(RuntimeError) as err: score_tiler._scores(slide) _tiles_generator.assert_called_once_with(score_tiler, slide) assert isinstance(err.value, RuntimeError) assert ( str(err.value) == "No tiles have been generated. This could happen if `check_tissue=True`" )
def it_knows_its_n_tiles(self): n_tiles = 4 score_tiler = ScoreTiler(RandomScorer(), (512, 512), n_tiles, 0) n_tiles_ = score_tiler.n_tiles assert type(n_tiles_) == int assert n_tiles_ == n_tiles
def it_knows_its_scorer(self): random_scorer = RandomScorer() score_tiler = ScoreTiler(random_scorer, (512, 512), 4, 0) scorer_ = score_tiler.scorer assert callable(scorer_) assert isinstance(scorer_, RandomScorer)
def it_can_calculate_highest_score_tiles(self, request, n_tiles, expected_value): slide = instance_mock(request, Slide) _scores = method_mock(request, ScoreTiler, "_scores") coords = CP(0, 10, 0, 10) _scores.return_value = [ (0.7, coords), (0.5, coords), (0.2, coords), (0.8, coords), (0.1, coords), ] _scorer = instance_mock(request, RandomScorer) score_tiler = ScoreTiler(_scorer, (10, 10), n_tiles, 0) highest_score_tiles = score_tiler._highest_score_tiles(slide) _scores.assert_called_once_with(score_tiler, slide) assert highest_score_tiles == expected_value
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, ScoreTiler, "_has_valid_tile_size") _has_valid_tile_size.return_value = False score_tiler = ScoreTiler(None, (50, 52), 2, 0) with pytest.raises(TileSizeError) as err: score_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(score_tiler, slide)
def but_it_raises_error_with_negative_n_tiles_value(self, request): slide = instance_mock(request, Slide) _scores = method_mock(request, ScoreTiler, "_scores") coords = CP(0, 10, 0, 10) _scores.return_value = [ (0.7, coords), (0.5, coords), (0.2, coords), (0.8, coords), (0.1, coords), ] _scorer = instance_mock(request, RandomScorer) score_tiler = ScoreTiler(_scorer, (10, 10), -1, 0) with pytest.raises(ValueError) as err: score_tiler.extract(slide) assert isinstance(err.value, ValueError) assert str(err.value) == "'n_tiles' cannot be negative (-1)"
def it_constructs_from_args(self, request): _init = initializer_mock(request, ScoreTiler) rs = RandomScorer() grid_tiler = ScoreTiler(rs, (512, 512), 4, 2, True, 0, "", ".png") _init.assert_called_once_with(ANY, rs, (512, 512), 4, 2, True, 0, "", ".png") assert isinstance(grid_tiler, ScoreTiler) assert isinstance(grid_tiler, GridTiler) assert isinstance(grid_tiler, Tiler)
def it_can_extract_score_tiles(self, request, tmpdir): _extract_tile = method_mock(request, Slide, "extract_tile") 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")) _highest_score_tiles = method_mock(request, ScoreTiler, "_highest_score_tiles") coords = CP(0, 10, 0, 10) tile = Tile(image, coords) _extract_tile.return_value = tile _highest_score_tiles.return_value = ( [(0.8, coords), (0.7, coords)], [(0.8, coords), (0.7, coords)], ) _tile_filename = method_mock(request, GridTiler, "_tile_filename") _tile_filename.side_effect = [ f"tile_{i}_level2_0-10-0-10.png" for i in range(2) ] _save_report = method_mock(request, ScoreTiler, "_save_report") random_scorer = RandomScorer() score_tiler = ScoreTiler(random_scorer, (10, 10), 2, 2) score_tiler.extract(slide) assert _extract_tile.call_args_list == [ call(slide, coords, 2), call(slide, coords, 2), ] _highest_score_tiles.assert_called_once_with(score_tiler, slide) assert _tile_filename.call_args_list == [ call(score_tiler, coords, 0), call(score_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") ) _save_report.assert_not_called()
def it_locates_tiles_on_the_slide(self, request, fixture_slide, expectation, tmpdir): slide = Slide(fixture_slide, os.path.join(tmpdir, "processed")) scored_tiles_extractor = ScoreTiler( scorer=NucleiScorer(), tile_size=(512, 512), n_tiles=2, level=0, check_tissue=True, ) expected_img = load_expectation( expectation, type_="png", ) tiles_location_img = scored_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_locates_tiles_on_the_slide( self, request, fixture_slide, tile_size, level, check_tissue, expectation, ): slide = Slide(fixture_slide, "") scored_tiles_extractor = ScoreTiler( scorer=NucleiScorer(), tile_size=tile_size, n_tiles=2, level=level, check_tissue=check_tissue, ) expected_img = load_expectation( expectation, type_="png", ) expected_warning_regex = ( r"Input image must be RGB. NOTE: the image will be converted to RGB before" r" HED conversion.") with pytest.warns(UserWarning, match=expected_warning_regex): # no binary mask object passed to locate_tiles # default value = BiggestTissueBoxMask tiles_location_img = scored_tiles_extractor.locate_tiles( slide, scale_factor=8) # --- 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)