Skip to content

Commit d94014f

Browse files
shaneahmedadamshephardpre-commit-ci[bot]
authored
✨ Add dict_to_store_semantic_segmentor (#926)
- Add a function to convert the output of TIAToolbox segmentation engine to AnnotationStore. --------- Co-authored-by: adamshephard <39619155+adamshephard@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 3eea490 commit d94014f

File tree

3 files changed

+387
-2
lines changed

3 files changed

+387
-2
lines changed

requirements/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
aiohttp>=3.8.1
44
albumentations>=1.3.0
55
bokeh>=3.1.1, <3.6.0
6-
Click>=8.1.3
6+
Click>=8.1.3, <8.2.0
77
defusedxml>=0.7.1
88
filelock>=3.9.0
99
flask>=2.2.2

tests/test_utils.py

Lines changed: 217 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import numpy as np
1313
import pandas as pd
1414
import pytest
15+
import shapely
1516
import tifffile
1617
import torch
1718
import zarr
@@ -24,6 +25,7 @@
2425
from tests.test_annotation_stores import cell_polygon
2526
from tiatoolbox import rcParam, utils
2627
from tiatoolbox.annotation.storage import DictionaryStore, SQLiteStore
28+
from tiatoolbox.enums import GeometryType
2729
from tiatoolbox.models.architecture import fetch_pretrained_weights
2830
from tiatoolbox.models.architecture.utils import compile_model
2931
from tiatoolbox.utils import misc
@@ -1864,7 +1866,221 @@ def test_torch_compile_compatibility(caplog: pytest.LogCaptureFixture) -> None:
18641866
assert "torch.compile" in caplog.text
18651867

18661868

1867-
# Tests for OME tiff writer
1869+
def test_dict_to_store_semantic_segment() -> None:
1870+
"""Tests multipoint behaviour in dict_to_store."""
1871+
test_pred = np.zeros(shape=(224, 224))
1872+
1873+
patch_output = {"predictions": test_pred}
1874+
1875+
store_ = misc.dict_to_store_semantic_segmentor(
1876+
patch_output=patch_output,
1877+
scale_factor=(1.0, 1.0),
1878+
class_dict=None,
1879+
save_path=None,
1880+
)
1881+
assert not store_.values()
1882+
1883+
# single point
1884+
patch_output["predictions"][100, 100] = 1
1885+
1886+
store_ = misc.dict_to_store_semantic_segmentor(
1887+
patch_output=patch_output,
1888+
scale_factor=(1.0, 1.0),
1889+
class_dict=None,
1890+
save_path=None,
1891+
)
1892+
assert len(store_) == 1
1893+
1894+
annotations_ = store_.values()
1895+
1896+
annotations_geometry_type = [
1897+
str(annotation_.geometry_type) for annotation_ in annotations_
1898+
]
1899+
1900+
assert "Point" in annotations_geometry_type
1901+
assert "Polygon" not in annotations_geometry_type
1902+
1903+
patch_output["predictions"][110:155, 110:115] = 1
1904+
1905+
store_ = misc.dict_to_store_semantic_segmentor(
1906+
patch_output=patch_output,
1907+
scale_factor=(1.0, 1.0),
1908+
class_dict=None,
1909+
save_path=None,
1910+
)
1911+
assert len(store_) == 2
1912+
1913+
annotations_ = store_.values()
1914+
1915+
annotations_geometry_type = [
1916+
str(annotation_.geometry_type) for annotation_ in annotations_
1917+
]
1918+
1919+
assert "Point" in annotations_geometry_type
1920+
assert "Polygon" in annotations_geometry_type
1921+
1922+
patch_output["predictions"][50, 50] = 1
1923+
patch_output["predictions"][50, 51] = 1
1924+
1925+
store_ = misc.dict_to_store_semantic_segmentor(
1926+
patch_output=patch_output,
1927+
scale_factor=(1.0, 1.0),
1928+
class_dict=None,
1929+
save_path=None,
1930+
)
1931+
assert len(store_) == 3
1932+
annotations_ = store_.values()
1933+
1934+
annotations_geometry_type = [
1935+
str(annotation_.geometry_type) for annotation_ in annotations_
1936+
]
1937+
1938+
assert "Point" in annotations_geometry_type
1939+
assert "Polygon" in annotations_geometry_type
1940+
assert "Line String" in annotations_geometry_type
1941+
1942+
1943+
def test_dict_to_store_semantic_segment_holes(tmp_path: Path) -> None:
1944+
"""Tests behaviour of holes in dict_to_store and save_path."""
1945+
test_pred = np.array(
1946+
[
1947+
[0, 0, 1, 0, 0],
1948+
[0, 1, 1, 1, 0],
1949+
[1, 1, 0, 1, 1],
1950+
[1, 1, 0, 1, 1],
1951+
[0, 1, 1, 1, 0],
1952+
[0, 0, 1, 0, 0],
1953+
]
1954+
)
1955+
1956+
patch_output = {"predictions": test_pred}
1957+
1958+
save_dir_path = tmp_path / "tmp"
1959+
save_dir_path.mkdir()
1960+
1961+
_ = misc.dict_to_store_semantic_segmentor(
1962+
patch_output=patch_output,
1963+
scale_factor=(1.0, 1.0),
1964+
class_dict=None,
1965+
save_path=save_dir_path,
1966+
)
1967+
1968+
assert save_dir_path.exists()
1969+
1970+
store_ = misc.dict_to_store_semantic_segmentor(
1971+
patch_output=patch_output,
1972+
scale_factor=(1.0, 1.0),
1973+
class_dict=None,
1974+
save_path=None,
1975+
)
1976+
1977+
# outer contour and inner contour/hole are now within the same geometry
1978+
assert len(store_) == 1, "There should be one geometry"
1979+
1980+
annotations_ = list(store_.values())
1981+
annotations_geometry_type = [
1982+
str(annotation_.geometry_type) for annotation_ in annotations_
1983+
]
1984+
assert "Polygon" in annotations_geometry_type
1985+
assert "Point" not in annotations_geometry_type
1986+
1987+
annotation = annotations_[0]
1988+
assert isinstance(annotation.geometry_type, GeometryType)
1989+
1990+
# Check number of holes
1991+
polygon = annotation.geometry
1992+
assert isinstance(polygon, shapely.geometry.polygon.Polygon), (
1993+
"The annotation should be a Polygon"
1994+
)
1995+
assert len(polygon.interiors) == 1, "There should be one hole in the Polygon"
1996+
1997+
1998+
def test_dict_to_store_semantic_segment_multiple_holes() -> None:
1999+
"""Tests behaviour of multiple holes in dict_to_store."""
2000+
test_pred = np.array(
2001+
[
2002+
[0, 0, 1, 0, 0],
2003+
[0, 1, 0, 1, 0],
2004+
[1, 1, 0, 1, 1],
2005+
[1, 1, 1, 1, 1],
2006+
[1, 1, 0, 1, 1],
2007+
[1, 1, 0, 1, 1],
2008+
[0, 1, 1, 1, 0],
2009+
[0, 0, 1, 0, 0],
2010+
]
2011+
)
2012+
2013+
patch_output = {"predictions": test_pred}
2014+
2015+
store_ = misc.dict_to_store_semantic_segmentor(
2016+
patch_output=patch_output,
2017+
scale_factor=(1.0, 1.0),
2018+
class_dict=None,
2019+
save_path=None,
2020+
)
2021+
2022+
# outer contour and inner contour/hole are now within the same geometry
2023+
assert len(store_) == 1, "There should be one geometry"
2024+
2025+
annotations_ = list(store_.values())
2026+
annotations_geometry_type = [
2027+
str(annotation_.geometry_type) for annotation_ in annotations_
2028+
]
2029+
assert "Polygon" in annotations_geometry_type
2030+
assert "Point" not in annotations_geometry_type
2031+
2032+
annotation = annotations_[0]
2033+
assert isinstance(annotation.geometry_type, GeometryType)
2034+
2035+
# Check number of holes
2036+
polygon = annotation.geometry
2037+
assert isinstance(polygon, shapely.geometry.polygon.Polygon), (
2038+
"The annotation should be a Polygon"
2039+
)
2040+
assert len(polygon.interiors) == 2, "There should be two holes in the Polygon"
2041+
2042+
2043+
def test_dict_to_store_semantic_segment_no_holes() -> None:
2044+
"""Tests behaviour of no holes in dict_to_store."""
2045+
test_pred = np.array(
2046+
[
2047+
[0, 0, 1, 0, 0],
2048+
[0, 1, 1, 1, 0],
2049+
[1, 1, 1, 1, 1],
2050+
[1, 1, 1, 1, 1],
2051+
[0, 1, 1, 1, 0],
2052+
[0, 0, 1, 0, 0],
2053+
]
2054+
)
2055+
2056+
patch_output = {"predictions": test_pred}
2057+
2058+
store_ = misc.dict_to_store_semantic_segmentor(
2059+
patch_output=patch_output,
2060+
scale_factor=(1.0, 1.0),
2061+
class_dict=None,
2062+
save_path=None,
2063+
)
2064+
2065+
# outer contour and inner contour/hole are now within the same geometry
2066+
assert len(store_) == 1, "There should be one geometry"
2067+
2068+
annotations_ = list(store_.values())
2069+
annotations_geometry_type = [
2070+
str(annotation_.geometry_type) for annotation_ in annotations_
2071+
]
2072+
assert "Polygon" in annotations_geometry_type
2073+
assert "Point" not in annotations_geometry_type
2074+
2075+
annotation = annotations_[0]
2076+
assert isinstance(annotation.geometry_type, GeometryType)
2077+
2078+
# Check number of holes
2079+
polygon = annotation.geometry
2080+
assert isinstance(polygon, shapely.geometry.polygon.Polygon), (
2081+
"The annotation should be a Polygon"
2082+
)
2083+
assert len(polygon.interiors) == 0, "There should be no holes in the Polygon"
18682084

18692085

18702086
def get_ome_metadata(tiff_path: Path) -> str | None:

0 commit comments

Comments
 (0)