Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add Support for QuPath Annotation Imports #721

Merged
merged 9 commits into from
Nov 22, 2023
53 changes: 53 additions & 0 deletions tests/test_annotation_stores.py
Original file line number Diff line number Diff line change
Expand Up @@ -2823,3 +2823,56 @@ def test_query_min_area(
_, store = fill_store(store_cls, ":memory:")
result = store.query((0, 0, 1000, 1000), min_area=1)
assert len(result) == 100 # should only get cells, pts are too small

@staticmethod
def test_import_from_qupath(
tmp_path: Path,
store_cls: type[AnnotationStore],
) -> None:
"""Test importing from a QuPath annotations file with measurements."""
# make a simple example of a .geojson exported from QuPath
anns = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[1076, 2322.55],
[1073.61, 2323.23],
[1072.58, 2323.88],
[1070.93, 2325.61],
[1076, 2322.55],
],
],
},
"properties": {
"object_type": "detection",
"isLocked": "false",
"measurements": [
{
"name": "Detection probability",
"value": 0.847621500492096,
},
{"name": "Area µm^2", "value": 27.739423751831055},
],
},
},
],
}
with (tmp_path / "test_annotations.geojson").open("w") as f:
json.dump(anns, f)
store = store_cls.from_geojson(
tmp_path / "test_annotations.geojson",
unpack_qupath_measurements=True,
)
assert len(store) == 1
ann = next(iter(store.values()))
assert ann.properties == {
"object_type": "detection",
"isLocked": "false",
"Detection probability": 0.847621500492096,
"Area µm^2": 27.739423751831055,
}
27 changes: 25 additions & 2 deletions tiatoolbox/annotation/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -1724,6 +1724,8 @@ def from_geojson(
fp: IO | str,
scale_factor: tuple[float, float] = (1, 1),
origin: tuple[float, float] = (0, 0),
*,
unpack_qupath_measurements: bool = False,
) -> AnnotationStore:
"""Create a new database with annotations loaded from a geoJSON file.

Expand All @@ -1736,21 +1738,31 @@ def from_geojson(
annotations saved at non-baseline resolution.
origin (Tuple[float, float]):
The x and y coordinates to use as the origin for the annotations.
unpack_qupath_measurements (bool):
If True, unpack QuPath measurements into individual properties of each
annotation. Defaults to False. Use only for .geojson exported by QuPath.

Returns:
AnnotationStore:
A new annotation store with the annotations loaded from the file.

"""
store = cls()
store.add_from_geojson(fp, scale_factor, origin=origin)
store.add_from_geojson(
fp,
scale_factor,
origin=origin,
unpack_qupath_measurements=unpack_qupath_measurements,
)
return store

def add_from_geojson(
self: AnnotationStore,
fp: IO | str,
scale_factor: tuple[float, float] = (1, 1),
origin: tuple[float, float] = (0, 0),
*,
unpack_qupath_measurements: bool = False,
) -> None:
"""Add annotations from a .geojson file to an existing store.

Expand All @@ -1765,6 +1777,9 @@ def add_from_geojson(
at non-baseline resolution.
origin (Tuple[float, float]):
The x and y coordinates to use as the origin for the annotations.
unpack_qupath_measurements (bool):
If True, unpack QuPath measurements into individual properties of each
annotation. Defaults to False. Use only for .geojson exported by QuPath.

"""

Expand All @@ -1782,6 +1797,14 @@ def transform_geometry(geom: Geometry) -> Geometry:
)
return geom

def unpack_qpath(props: dict) -> dict:
"""Helper function to unpack QuPath measurements."""
if unpack_qupath_measurements and "measurements" in props:
measurements = props.pop("measurements")
for m in measurements:
props[m["name"]] = m["value"]
return props

geojson = self._load_cases(
fp=fp,
string_fn=json.loads,
Expand All @@ -1793,7 +1816,7 @@ def transform_geometry(geom: Geometry) -> Geometry:
transform_geometry(
feature2geometry(feature["geometry"]),
),
feature["properties"],
unpack_qpath(feature["properties"]),
)
for feature in geojson["features"]
]
Expand Down
Loading