Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/histolab/slide.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def level_dimensions(self, level: int = 0) -> Tuple[int, int]:
-------
dimensions : tuple (width, height)
"""
level = level if level >= 0 else self._remap_level(level)
try:
return self._wsi.level_dimensions[level]
except IndexError:
Expand Down Expand Up @@ -406,6 +407,31 @@ def _has_valid_coords(self, coords: CoordinatePair) -> bool:
and 0 <= coords.y_br < self.dimensions[1]
)

def _remap_level(self, level: int) -> int:
"""Remap negative index for the given level onto a positive one.

Parameters
----------
level : int
the level index to remap

Raises
------
LevelError
when the abs(level) is greater than the number of the levels.

Returns
-------
level : int
positive level index
"""
if len(self.levels) - abs(level) < 0:
raise LevelError(
f"Level {level} not available. Number of available levels: "
f"{len(self._wsi.level_dimensions)}"
)
return len(self.levels) - abs(level)

def _resample(self, scale_factor: int = 32) -> Tuple[PIL.Image.Image, np.array]:
"""Converts a slide to a scaled-down PIL image.

Expand Down
22 changes: 1 addition & 21 deletions src/histolab/tiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,16 +239,6 @@ def extract(self, slide: Slide):

print(f"{tiles_counter} Grid Tiles have been saved.")

@property
def level(self) -> int:
return self._valid_level

@level.setter
def level(self, level_: int):
if level_ < 0:
raise LevelError(f"Level cannot be negative ({level_})")
self._valid_level = level_

@property
def tile_size(self) -> Tuple[int, int]:
return self._valid_tile_size
Expand Down Expand Up @@ -462,7 +452,7 @@ def extract(self, slide: Slide):
LevelError
If the level is not available for the slide
"""
if self.level not in slide.levels:
if abs(self.level) not in slide.levels:
raise LevelError(
f"Level {self.level} not available. Number of available levels: "
f"{len(slide.levels)}"
Expand All @@ -482,16 +472,6 @@ def extract(self, slide: Slide):
print(f"\t Tile {tiles_counter} saved: {tile_filename}")
print(f"{tiles_counter+1} Random Tiles have been saved.")

@property
def level(self) -> int:
return self._valid_level

@level.setter
def level(self, level_: int):
if level_ < 0:
raise LevelError(f"Level cannot be negative ({level_})")
self._valid_level = level_

@property
def max_iter(self) -> int:
return self._valid_max_iter
Expand Down
52 changes: 43 additions & 9 deletions tests/integration/test_tiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,51 @@

class DescribeRandomTiler:
@pytest.mark.parametrize(
"fixture_slide, check_tissue, expectation",
"fixture_slide, level, check_tissue, expectation",
[
(
SVS.CMU_1_SMALL_REGION,
0,
False,
"tiles-location-images/cmu-1-small-region-tl-random-false",
),
(
SVS.TCGA_CR_7395_01A_01_TS1,
-2,
False,
"tiles-location-images/tcga-cr-7395-01a-01-ts1-tl-random-false",
),
(
SVS.TCGA_CR_7395_01A_01_TS1,
0,
False,
"tiles-location-images/tcga-cr-7395-01a-01-ts1-tl-random-false",
),
(
SVS.CMU_1_SMALL_REGION,
0,
True,
"tiles-location-images/cmu-1-small-region-tl-random-true",
),
(
SVS.TCGA_CR_7395_01A_01_TS1,
0,
True,
"tiles-location-images/tcga-cr-7395-01a-01-ts1-tl-random-true",
),
],
)
def it_locates_tiles_on_the_slide(
self, request, fixture_slide, check_tissue, expectation, tmpdir
self, request, fixture_slide, level, check_tissue, expectation, tmpdir
):
slide = Slide(fixture_slide, os.path.join(tmpdir, "processed"))
slide.save_scaled_image(10)
random_tiles_extractor = RandomTiler(
tile_size=(512, 512), n_tiles=2, level=0, seed=42, check_tissue=check_tissue
tile_size=(512, 512),
n_tiles=2,
level=level,
seed=42,
check_tissue=check_tissue,
)
expected_img = load_expectation(
expectation,
Expand All @@ -60,37 +74,47 @@ def it_locates_tiles_on_the_slide(

class DescribeGridTiler:
@pytest.mark.parametrize(
"fixture_slide, check_tissue, expectation",
"fixture_slide, level,check_tissue, expectation",
[
(
SVS.CMU_1_SMALL_REGION,
0,
False,
"tiles-location-images/cmu-1-small-region-tl-grid-false",
),
(
SVS.TCGA_CR_7395_01A_01_TS1,
-2,
False,
"tiles-location-images/tcga-cr-7395-01a-01-ts1-tl-grid-false",
),
(
SVS.TCGA_CR_7395_01A_01_TS1,
0,
False,
"tiles-location-images/tcga-cr-7395-01a-01-ts1-tl-grid-false",
),
(
SVS.CMU_1_SMALL_REGION,
0,
True,
"tiles-location-images/cmu-1-small-region-tl-grid-true",
),
(
SVS.TCGA_CR_7395_01A_01_TS1,
0,
True,
"tiles-location-images/tcga-cr-7395-01a-01-ts1-tl-grid-true",
),
],
)
def it_locates_tiles_on_the_slide(
self, request, fixture_slide, check_tissue, expectation, tmpdir
self, request, fixture_slide, level, check_tissue, expectation, tmpdir
):
slide = Slide(fixture_slide, os.path.join(tmpdir, "processed"))
grid_tiles_extractor = GridTiler(
tile_size=(512, 512),
level=0,
level=level,
check_tissue=check_tissue,
)
expected_img = load_expectation(expectation, type_="png")
Expand All @@ -103,39 +127,49 @@ def it_locates_tiles_on_the_slide(

class DescribeScoreTiler:
@pytest.mark.parametrize(
"fixture_slide, check_tissue, expectation",
"fixture_slide, level, check_tissue, expectation",
[
(
SVS.CMU_1_SMALL_REGION,
0,
False,
"tiles-location-images/cmu-1-small-region-tl-scored-false",
),
(
SVS.TCGA_CR_7395_01A_01_TS1,
-2,
False,
"tiles-location-images/tcga-cr-7395-01a-01-ts1-tl-scored-false",
),
(
SVS.TCGA_CR_7395_01A_01_TS1,
0,
False,
"tiles-location-images/tcga-cr-7395-01a-01-ts1-tl-scored-false",
),
(
SVS.CMU_1_SMALL_REGION,
0,
True,
"tiles-location-images/cmu-1-small-region-tl-scored-true",
),
(
SVS.TCGA_CR_7395_01A_01_TS1,
0,
True,
"tiles-location-images/tcga-cr-7395-01a-01-ts1-tl-scored-true",
),
],
)
def it_locates_tiles_on_the_slide(
self, request, fixture_slide, check_tissue, expectation, tmpdir
self, request, fixture_slide, level, check_tissue, 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,
level=level,
check_tissue=check_tissue,
)
expected_img = load_expectation(
Expand Down
47 changes: 41 additions & 6 deletions tests/unit/test_slide.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,28 +496,35 @@ def but_it_raises_error_when_it_doesnt_exist(self):
f"directory: {repr(os.path.join('processed', 'thumbnails', 'b.png'))}"
)

def it_knows_its_level_dimensions(self, tmpdir):
@pytest.mark.parametrize(
"level, expected_value", ((0, (500, 500)), (-1, (500, 500)))
)
def it_knows_its_level_dimensions(self, level, expected_value, 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")

level_dimensions = slide.level_dimensions(level=0)
level_dimensions = slide.level_dimensions(level=level)

assert level_dimensions == (500, 500)
assert level_dimensions == expected_value

def but_it_raises_expection_when_level_does_not_exist(self, tmpdir):
@pytest.mark.parametrize("level", (3, -3))
def but_it_raises_expection_when_level_does_not_exist(self, level, 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")
with pytest.raises(LevelError) as err:
slide.level_dimensions(level=3)
slide.level_dimensions(level=level)

assert isinstance(err.value, LevelError)
assert str(err.value) == "Level 3 not available. Number of available levels: 1"
assert (
str(err.value)
== f"Level {level} not available. Number of available levels: 1"
)

@pytest.mark.parametrize(
"coords, expected_result",
Expand Down Expand Up @@ -558,8 +565,36 @@ def it_can_access_to_its_properties(self, request):

assert slide.properties == {"foo": "bar"}

@pytest.mark.parametrize("level, expected_value", ((-1, 8), (-2, 7), (-9, 0)))
def it_can_remap_negative_level_indices(self, level, expected_value, levels_prop):
levels_prop.return_value = [0, 1, 2, 3, 4, 5, 6, 7, 8]

slide = Slide("path", "processed")

assert slide._remap_level(level) == expected_value

def but_it_raises_a_level_error_when_it_cannot_be_mapped(self, tmpdir, levels_prop):
levels_prop.return_value = [0, 1, 2, 3, 4, 5, 6, 7, 8]
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")

with pytest.raises(LevelError) as err:
slide._remap_level(-10)

assert isinstance(err.value, LevelError)
assert (
str(err.value) == "Level -10 not available. Number of available levels: 1"
)

# fixture components ---------------------------------------------

@pytest.fixture
def levels_prop(self, request):
return property_mock(request, Slide, "levels")

@pytest.fixture
def resampled_dims_(self, request):
return method_mock(request, Slide, "_resampled_dimensions")
Expand Down
28 changes: 8 additions & 20 deletions tests/unit/test_tiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@


class Describe_RandomTiler:
def it_constructs_from_args(self, request):
@pytest.mark.parametrize("level", (2, -2))
def it_constructs_from_args(self, level, request):
_init = initializer_mock(request, RandomTiler)

random_tiler = RandomTiler((512, 512), 10, 2, 7, True, "", ".png", int(1e4))
random_tiler = RandomTiler((512, 512), 10, level, 7, True, "", ".png", int(1e4))

_init.assert_called_once_with(
ANY, (512, 512), 10, 2, 7, True, "", ".png", int(1e4)
ANY, (512, 512), 10, level, 7, True, "", ".png", int(1e4)
)
assert isinstance(random_tiler, RandomTiler)
assert isinstance(random_tiler, Tiler)
Expand All @@ -57,13 +58,6 @@ def or_it_has_not_available_level_value(self, tmpdir):
assert isinstance(err.value, LevelError)
assert str(err.value) == "Level 3 not available. Number of available levels: 1"

def or_it_has_negative_level_value(self):
with pytest.raises(LevelError) as err:
RandomTiler((512, 512), 10, -1)

assert isinstance(err.value, LevelError)
assert str(err.value) == "Level cannot be negative (-1)"

def or_it_has_wrong_max_iter(self):
with pytest.raises(ValueError) as err:
RandomTiler((512, 512), 10, 0, max_iter=3)
Expand Down Expand Up @@ -449,12 +443,13 @@ def _random_tile_coordinates(self, request):


class Describe_GridTiler:
def it_constructs_from_args(self, request):
@pytest.mark.parametrize("level", (2, -2))
def it_constructs_from_args(self, level, request):
_init = initializer_mock(request, GridTiler)

grid_tiler = GridTiler((512, 512), 2, True, 0, "", ".png")
grid_tiler = GridTiler((512, 512), level, True, 0, "", ".png")

_init.assert_called_once_with(ANY, (512, 512), 2, True, 0, "", ".png")
_init.assert_called_once_with(ANY, (512, 512), level, True, 0, "", ".png")
assert isinstance(grid_tiler, GridTiler)
assert isinstance(grid_tiler, Tiler)

Expand All @@ -479,13 +474,6 @@ def or_it_has_not_available_level_value(self, tmpdir):
assert isinstance(err.value, LevelError)
assert str(err.value) == "Level 3 not available. Number of available levels: 1"

def or_it_has_negative_level_value(self):
with pytest.raises(LevelError) as err:
GridTiler((512, 512), -1)

assert isinstance(err.value, LevelError)
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, tile_size):
grid_tiler = GridTiler(tile_size, 10, True, 0)
Expand Down