Skip to content

Commit

Permalink
Merge 9d7b12e into 7cc6af5
Browse files Browse the repository at this point in the history
  • Loading branch information
alessiamarcolini committed Oct 13, 2020
2 parents 7cc6af5 + 9d7b12e commit 633e1a4
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 10 deletions.
11 changes: 8 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
- brew update
- brew install openslide
- echo 'export PATH="/usr/local/Frameworks/Python.framework/Versions/Current/bin:$PATH"' >> /Users/travis/.bash_profile
- source ~/.bash_profile
- echo $PATH
env: CAN_FAIL=true
- name: "Python 3.7.4 on macOS"
Expand All @@ -30,6 +31,7 @@ jobs:
before_install:
- brew install openslide
- echo 'export PATH="/usr/local/Frameworks/Python.framework/Versions/Current/bin:$PATH"' >> /Users/travis/.bash_profile
- source ~/.bash_profile
- echo $PATH
- name: "Python 3.8.3 on macOS"
os: osx
Expand All @@ -38,6 +40,7 @@ jobs:
before_install:
- brew install openslide
- echo 'export PATH="/usr/local/Frameworks/Python.framework/Versions/Current/bin:$PATH"' >> /Users/travis/.bash_profile
- source ~/.bash_profile
- echo $PATH
- name: "Python 3.6.8 on Windows"
os: windows # Windows 10.0.17134 N/A Build 17134
Expand Down Expand Up @@ -69,16 +72,18 @@ jobs:
- wget https://github.com/openslide/openslide-winbuild/releases/download/v20171122/openslide-win64-20171122.zip -P /c/downloads
- 7z e /c/downloads/openslide-win64-20171122.zip -aoa
- export "PATH=/c/Python38/Scripts:/c/downloads/openslide-win64-20171122/bin:$PATH"
- echo alias python3=\'python\' >> ~.bashrc
- source ~/.bashrc
env: CAN_FAIL=true
allow_failures:
- env: CAN_FAIL=true
fast_finish: true

# command to install dependencies
install:
- pip3 install -e .[testing]
- pip3 install flake8
- pip3 install pooch
- python3 -m pip install -e .[testing]
- python3 -m pip install flake8
- python3 -m pip install pooch

services:
- xvfb
Expand Down
44 changes: 39 additions & 5 deletions src/histolab/filters/morphological_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ def __init__(
self.avoid_overmask = avoid_overmask
self.overmask_thresh = overmask_thresh

def __call__(self, np_img) -> np.ndarray:
def __call__(self, np_mask: np.ndarray) -> np.ndarray:
return F.remove_small_objects(
np_img, self.min_size, self.avoid_overmask, self.overmask_thresh
np_mask, self.min_size, self.avoid_overmask, self.overmask_thresh
)

def __repr__(self):
Expand All @@ -85,7 +85,7 @@ class RemoveSmallHoles:
def __init__(self, area_threshold: int = 3000):
self.area_threshold = area_threshold

def __call__(self, np_mask) -> np.ndarray:
def __call__(self, np_mask: np.ndarray) -> np.ndarray:
return skimage.morphology.remove_small_holes(np_mask, self.area_threshold)

def __repr__(self):
Expand Down Expand Up @@ -208,7 +208,7 @@ def __init__(self, disk_size: int = 3, iterations: int = 1):
self.disk_size = disk_size
self.iterations = iterations

def __call__(self, np_mask) -> np.ndarray:
def __call__(self, np_mask: np.ndarray) -> np.ndarray:
if not np.array_equal(np_mask, np_mask.astype(bool)):
raise ValueError("Mask must be binary")
return scipy.ndimage.morphology.binary_opening(
Expand Down Expand Up @@ -244,7 +244,7 @@ def __init__(self, disk_size: int = 3, iterations: int = 1):
self.disk_size = disk_size
self.iterations = iterations

def __call__(self, np_mask) -> np.ndarray:
def __call__(self, np_mask: np.ndarray) -> np.ndarray:
if not np.array_equal(np_mask, np_mask.astype(bool)):
raise ValueError("Mask must be binary")
return scipy.ndimage.morphology.binary_closing(
Expand All @@ -255,6 +255,40 @@ def __repr__(self):
return self.__class__.__name__ + "()"


class WatershedSegmentation:
"""Segment and label an binary mask with Watershed segmentation [1]_
The watershed algorithm treats pixels values as a local topography (elevation).
Parameters
----------
np_mask : np.ndarray
Input mask
region_shape : int, optional
The local region within which to search for image peaks is defined as a squared
area region_shape x region_shape. Default is 6.
Returns
-------
np.ndarray
Labelled segmentation mask
References
--------
.. [1] Watershed segmentation.
https://scikit-image.org/docs/dev/auto_examples/segmentation/plot_watershed.html
"""

def __init__(self, region_shape: int = 6) -> None:
self.region_shape = region_shape

def __call__(self, np_mask: np.ndarray) -> np.ndarray:
return F.watershed_segmentation(np_mask, self.region_shape)

def __repr__(self) -> str:
return self.__class__.__name__ + "()"


class WhiteTopHat:
"""Return white top hat of an image.
Expand Down
38 changes: 38 additions & 0 deletions src/histolab/filters/morphological_filters_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
# ------------------------------------------------------------------------

import numpy as np
import scipy.ndimage as sc_ndimage
import skimage.feature as sk_feature
import skimage.morphology as sk_morphology
import skimage.segmentation as sk_segmentation

from .util import mask_percent

Expand Down Expand Up @@ -60,3 +63,38 @@ def remove_small_objects(
np_mask, new_min_size, avoid_overmask, overmask_thresh
)
return mask_no_small_object


def watershed_segmentation(np_mask: np.ndarray, region_shape: int = 6) -> np.ndarray:
"""Segment and label an binary mask with Watershed segmentation [1]_
The watershed algorithm treats pixels values as a local topography (elevation).
Parameters
----------
np_mask : np.ndarray
Input mask
region_shape : int, optional
The local region within which to search for image peaks is defined as a squared
area region_shape x region_shape. Default is 6.
Returns
-------
np.ndarray
Labelled segmentation mask
References
--------
.. [1] Watershed segmentation.
https://scikit-image.org/docs/dev/auto_examples/segmentation/plot_watershed.html
"""
distance = sc_ndimage.distance_transform_edt(np_mask)
local_maxi = sk_feature.peak_local_max(
distance,
indices=False,
footprint=np.ones((region_shape, region_shape)),
labels=np_mask,
)
markers = sc_ndimage.label(local_maxi)[0]
labels = sk_segmentation.watershed(-distance, markers, mask=np_mask)
return labels
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added tests/fixtures/mask-arrays/ytma1.npy
Binary file not shown.
Binary file added tests/fixtures/mask-arrays/ytma2.npy
Binary file not shown.
18 changes: 18 additions & 0 deletions tests/integration/test_morphological_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,21 @@ def test_remove_small_objects_filter(

np.testing.assert_array_equal(mask_no_small_object, expected_value)
assert type(mask_no_small_object) == np.ndarray


@pytest.mark.parametrize(
"mask_array, region_shape, expected_array",
(
(MASKNPY.YTMA1, 6, "mask-arrays/ytma1-watershed-segmentation-region6"),
(MASKNPY.YTMA2, 6, "mask-arrays/ytma2-watershed-segmentation-region6"),
(MASKNPY.YTMA1, 3, "mask-arrays/ytma1-watershed-segmentation-region3"),
(MASKNPY.YTMA2, 3, "mask-arrays/ytma2-watershed-segmentation-region3"),
),
)
def test_watershed_segmentation_filter(mask_array, region_shape, expected_array):
expected_value = load_expectation(expected_array, type_="npy")

mask_watershed = mof.watershed_segmentation(mask_array, region_shape)

np.testing.assert_array_equal(mask_watershed, expected_value)
assert type(mask_watershed) == np.ndarray
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# encoding: utf-8

import pytest

import numpy as np
import pytest
import skimage.morphology

from histolab.filters import morphological_filters as mof

from ...base import IMAGE1_RGB, IMAGE2_RGBA
Expand Down Expand Up @@ -179,3 +179,17 @@ def it_calls_white_top_hat_filter(self, request):
np.testing.assert_array_equal(_white_top_hat.call_args_list[0][0][0], mask_arr)
np.testing.assert_array_equal(_white_top_hat.call_args_list[0][0][1], disk)
assert type(white_top_hat(mask_arr)) == np.ndarray

def it_calls_watershed_segmentation_functional(self, request):
mask_arr = NpArrayMock.ONES_500X500X4_BOOL
F_watershed_segmentation = function_mock(
request,
"histolab.filters.morphological_filters_functional.watershed_segmentation",
)
F_watershed_segmentation.return_value = mask_arr
watershed_segmentation = mof.WatershedSegmentation()

watershed_segmentation(mask_arr)

F_watershed_segmentation.assert_called_once_with(mask_arr, 6)
assert type(watershed_segmentation(mask_arr)) == np.ndarray

0 comments on commit 633e1a4

Please sign in to comment.