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
57 changes: 54 additions & 3 deletions src/histolab/scorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from .filters import image_filters as imf
from .filters import morphological_filters as mof
from .filters.util import mask_difference
from .masks import TissueMask
from .tile import Tile

try:
Expand Down Expand Up @@ -66,15 +67,65 @@ def __call__(self, tile: Tile) -> float:
return np.random.random()


class CellularityScorer(Scorer):
"""Implement a Scorer that estimates the cellularity in an H&E-stained tile.

This class deconvolves the hematoxylin channel and uses the fraction of tile
occupied by hematoxylin as the cellularity score.

Notice that this scorer is useful when tiles are extracted at a very low resolution
with no artifacts; in this case, using the``NucleiScorer()`` instead would not
work well as nuclei are no discernible at low magnification.

.. automethod:: __call__

Parameters
----------
consider_tissue : bool, optional
Whether the detected tissue on the tile should be considered to compute the
cellularity score. Default is True
"""

def __init__(self, consider_tissue: bool = True) -> None:
self.consider_tissue = consider_tissue

def __call__(self, tile: Tile) -> float:
"""Return the tile cellularity score.

Parameters
----------
tile : Tile
The tile to calculate the score from.
consider_tissue : bool
Whether the cellularity score should be computed by considering the tissue
on the tile. Default is True

Returns
-------
float
Cellularity score
"""

tissue_mask = TissueMask()
filters_cellularity = imf.Compose(
[imf.HematoxylinChannel(), imf.YenThreshold(operator.gt)]
)

mask_nuclei = np.array(tile.apply_filters(filters_cellularity).image)

return (
np.count_nonzero(mask_nuclei) / np.count_nonzero(tissue_mask(tile))
if self.consider_tissue
else np.count_nonzero(mask_nuclei) / mask_nuclei.size
)


class NucleiScorer(Scorer):
r"""Implement a Scorer that estimates the presence of nuclei in an H&E-stained tile.

This class implements an hybrid algorithm that combines thresholding and
morphological operations to segment nuclei on H&E-stained histological images.

This class implements an hybrid algorithm that combines thresholding and
morphological operations to segment nuclei on H&E-stained histological images.

The NucleiScorer class defines the score of a given tile t as:

.. math::
Expand Down
99 changes: 99 additions & 0 deletions tests/integration/test_scorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,102 @@ def it_knows_nuclei_score(self, tile_img, expected_score):
score = nuclei_scorer(tile)

assert round(score, 5) == round(expected_score, 5)

@pytest.mark.parametrize(
"tile_img, tissue, expected_score",
(
# level 0
(TILES.VERY_LOW_NUCLEI_SCORE_LEVEL0, True, 8.237973509211956e-05),
(TILES.LOW_NUCLEI_SCORE_LEVEL0, True, 0.029559623811739984),
(TILES.MEDIUM_NUCLEI_SCORE_LEVEL0, True, 0.030890383076685523),
(TILES.HIGH_NUCLEI_SCORE_LEVEL0, True, 0.5452530357003211),
# level 1
(
TILES.VERY_LOW_NUCLEI_SCORE_RED_PEN_LEVEL1,
True,
0.01610148106450298,
), # breast - red pen
(
TILES.LOW_NUCLEI_SCORE_LEVEL1,
True,
0.1887327689375471,
), # breast - green pen
(TILES.MEDIUM_NUCLEI_SCORE_LEVEL1, True, 0.04807442622295907), # aorta
(
TILES.MEDIUM_NUCLEI_SCORE_LEVEL1_2,
True,
0.23493428091089713,
), # breast - green pen
(
TILES.MEDIUM_NUCLEI_SCORE_GREEN_PEN_LEVEL1,
True,
0.6710679905294349,
), # breast - green pen
(
TILES.HIGH_NUCLEI_SCORE_RED_PEN_LEVEL1,
True,
0.30155274716806657,
), # breast - red pen
# level 2
(TILES.MEDIUM_NUCLEI_SCORE_LEVEL2, True, 0.40466129363258146), # prostate
(TILES.HIGH_NUCLEI_SCORE_LEVEL2, True, 0.906540012633011), # prostate
# no tissue
(TILES.NO_TISSUE, True, 0.05299860529986053),
(TILES.NO_TISSUE2, True, 0.003337363966142684),
(TILES.NO_TISSUE_LINE, True, 0.3471366793342943),
(TILES.NO_TISSUE_RED_PEN, True, 0.7135526324697217),
(TILES.NO_TISSUE_GREEN_PEN, True, 0.6917688745017198),
(TILES.VERY_LOW_NUCLEI_SCORE_LEVEL0, False, 8.168825750149552e-05),
(TILES.LOW_NUCLEI_SCORE_LEVEL0, False, 0.024471282958984375),
(TILES.MEDIUM_NUCLEI_SCORE_LEVEL0, False, 0.028293456864885436),
(TILES.HIGH_NUCLEI_SCORE_LEVEL0, False, 0.5430199644432031),
# level 1
(
TILES.VERY_LOW_NUCLEI_SCORE_RED_PEN_LEVEL1,
False,
0.006328582763671875,
), # breast - red pen
(
TILES.LOW_NUCLEI_SCORE_LEVEL1,
False,
0.064971923828125,
), # breast - green pen
(TILES.MEDIUM_NUCLEI_SCORE_LEVEL1, False, 0.036724090576171875),
(
TILES.MEDIUM_NUCLEI_SCORE_LEVEL1_2,
False,
0.23455429077148438,
), # breast - green pen
(
TILES.MEDIUM_NUCLEI_SCORE_GREEN_PEN_LEVEL1,
False,
0.5416870117187,
), # breast - green pen
(
TILES.HIGH_NUCLEI_SCORE_RED_PEN_LEVEL1,
False,
0.2985572814941406,
), # breast - red pen
# level 2
(TILES.MEDIUM_NUCLEI_SCORE_LEVEL2, False, 0.3309669494628906), # prostate
(TILES.HIGH_NUCLEI_SCORE_LEVEL2, False, 0.14234542846679688), # prostate
# no tissue
(TILES.NO_TISSUE, False, 0.0002899169921875),
(TILES.NO_TISSUE2, False, 0.000263214111328125),
(TILES.NO_TISSUE_LINE, False, 0.010105133056640625),
(TILES.NO_TISSUE_RED_PEN, False, 0.4020729064941406),
(TILES.NO_TISSUE_GREEN_PEN, False, 0.48259735107421875),
),
)
def it_knows_cellularity_score(self, tile_img, tissue, expected_score):
tile = Tile(tile_img, None)
cell_scorer = scorer.CellularityScorer(consider_tissue=tissue)
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):
score = cell_scorer(tile)

assert round(score, 5) == round(expected_score, 5)