Skip to content

Commit 62968b3

Browse files
committed
find-contours now works on any image data
1 parent f107181 commit 62968b3

3 files changed

Lines changed: 49 additions & 17 deletions

File tree

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Changelog
66

77
- switched from `imgaug3` to `imaug` for numpy 2 support
88
- added `roi-images` filter for extracting sub-images based on object-detection bounding boxes
9+
- the `find-contours` filter now works on any type of image data
910

1011

1112
0.0.9 (2025-04-03)

plugins/find-contours.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
# find-contours
22

3-
* accepts: idc.api.ImageSegmentationData
3+
* accepts: idc.api.ImageData
44
* generates: idc.api.ObjectDetectionData
55

6-
Detects blobs in the annotations of the image segmentation data and turns them into object detection polygons.
6+
Detects blobs images and turns them into object detection polygons. In case of image segmentation data, the annotations are analyzed, otherwise the base image.
77

88
```
99
usage: find-contours [-h] [-l {DEBUG,INFO,WARNING,ERROR,CRITICAL}]
1010
[-N LOGGER_NAME] [-t MASK_THRESHOLD] [-n MASK_NTH]
11-
[-m VIEW_MARGIN] [-f {low,high}]
11+
[-m VIEW_MARGIN] [-f {low,high}] [--label LABEL]
1212
13-
Detects blobs in the annotations of the image segmentation data and turns them
14-
into object detection polygons.
13+
Detects blobs images and turns them into object detection polygons. In case of
14+
image segmentation data, the annotations are analyzed, otherwise the base
15+
image.
1516
1617
options:
1718
-h, --help show this help message and exit
@@ -34,4 +35,6 @@ options:
3435
-f {low,high}, --fully_connected {low,high}
3536
Whether regions of high or low values should be fully-
3637
connected at isthmuses. (default: low)
38+
--label LABEL The label to use when processing images other than
39+
image segmentation ones. (default: object)
3740
```

src/idc/imgaug/filter/_find_contours.py

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from wai.logging import LOGGING_WARNING
77
from wai.common.geometry import Polygon, Point
88
from wai.common.adams.imaging.locateobjects import LocatedObject, LocatedObjects
9-
from idc.api import ObjectDetectionData, ImageSegmentationData, make_list, flatten_list, LABEL_KEY
9+
from idc.api import ImageData, ObjectDetectionData, ImageSegmentationData, make_list, flatten_list, LABEL_KEY, \
10+
safe_deepcopy
1011
from smu import mask_to_polygon, polygon_to_lists
1112

1213

@@ -24,7 +25,7 @@ class FindContours(Filter):
2425
"""
2526

2627
def __init__(self, mask_threshold: float = 0.1, mask_nth: int = 1,
27-
view_margin: int = 5, fully_connected: str = "low",
28+
view_margin: int = 5, fully_connected: str = "low", label: str = None,
2829
logger_name: str = None, logging_level: str = LOGGING_WARNING):
2930
"""
3031
Initializes the filter.
@@ -37,6 +38,8 @@ def __init__(self, mask_threshold: float = 0.1, mask_nth: int = 1,
3738
:type view_margin: int
3839
:param fully_connected: whether regions of high or low values should be fully-connected at isthmuses
3940
:type fully_connected: str
41+
:param label: the label to use when processing images other than image segmentation
42+
:type label: str
4043
:param logger_name: the name to use for the logger
4144
:type logger_name: str
4245
:param logging_level: the logging level to use
@@ -47,6 +50,7 @@ def __init__(self, mask_threshold: float = 0.1, mask_nth: int = 1,
4750
self.mask_nth = mask_nth
4851
self.view_margin = view_margin
4952
self.fully_connected = fully_connected
53+
self.label = label
5054

5155
def name(self) -> str:
5256
"""
@@ -64,7 +68,7 @@ def description(self) -> str:
6468
:return: the description
6569
:rtype: str
6670
"""
67-
return "Detects blobs in the annotations of the image segmentation data and turns them into object detection polygons."
71+
return "Detects blobs images and turns them into object detection polygons. In case of image segmentation data, the annotations are analyzed, otherwise the base image."
6872

6973
def accepts(self) -> List:
7074
"""
@@ -73,7 +77,7 @@ def accepts(self) -> List:
7377
:return: the list of classes
7478
:rtype: list
7579
"""
76-
return [ImageSegmentationData]
80+
return [ImageData]
7781

7882
def generates(self) -> List:
7983
"""
@@ -96,6 +100,7 @@ def _create_argparser(self) -> argparse.ArgumentParser:
96100
parser.add_argument("-n", "--mask_nth", type=float, help="The contour tracing can be slow for large masks, by using only every nth row/col, this can be sped up dramatically.", default=1, required=False)
97101
parser.add_argument("-m", "--view_margin", type=int, help="The margin in pixels to enlarge the view with in each direction.", default=5, required=False)
98102
parser.add_argument("-f", "--fully_connected", choices=CONNECTIVITY, help="Whether regions of high or low values should be fully-connected at isthmuses.", default=CONNECTIVITY_LOW, required=False)
103+
parser.add_argument("--label", type=str, help="The label to use when processing images other than image segmentation ones.", default="object", required=False)
99104
return parser
100105

101106
def _apply_args(self, ns: argparse.Namespace):
@@ -110,6 +115,7 @@ def _apply_args(self, ns: argparse.Namespace):
110115
self.mask_nth = ns.mask_nth
111116
self.view_margin = ns.view_margin
112117
self.fully_connected = ns.fully_connected
118+
self.label = ns.label
113119

114120
def initialize(self):
115121
"""
@@ -124,6 +130,8 @@ def initialize(self):
124130
self.view_margin = 5
125131
if self.fully_connected is None:
126132
self.fully_connected = CONNECTIVITY_LOW
133+
if self.label is None:
134+
self.label = "object"
127135

128136
def _do_process(self, data):
129137
"""
@@ -135,12 +143,32 @@ def _do_process(self, data):
135143
result = []
136144
for item in make_list(data):
137145
objs = LocatedObjects()
138-
for i, label in enumerate(item.annotation.labels):
139-
if label not in item.annotation.layers:
140-
continue
141-
layer = item.annotation.layers[label]
142-
layer = np.where(layer == 255, 1, 0)
143-
polys = mask_to_polygon(layer, mask_threshold=self.mask_threshold, mask_nth=self.mask_nth,
146+
if isinstance(item, ImageSegmentationData):
147+
for i, label in enumerate(item.annotation.labels):
148+
if label not in item.annotation.layers:
149+
continue
150+
layer = item.annotation.layers[label]
151+
layer = np.where(layer == 255, 1, 0)
152+
polys = mask_to_polygon(layer, mask_threshold=self.mask_threshold, mask_nth=self.mask_nth,
153+
fully_connected=self.fully_connected)
154+
for poly in polys:
155+
px, py = polygon_to_lists(poly, swap_x_y=True, as_type="int")
156+
left = min(px)
157+
right = max(px)
158+
top = min(py)
159+
bottom = max(py)
160+
points = [Point(x, y) for x, y in zip(px, py)]
161+
polygon = Polygon(*points)
162+
obj = LocatedObject(left, top, right - left + 1, bottom - top + 1)
163+
obj.metadata[LABEL_KEY] = label
164+
obj.set_polygon(polygon)
165+
objs.append(obj)
166+
item_new = ObjectDetectionData(source=item.source, image_name=item.image_name, data=safe_deepcopy(item.data),
167+
annotation=objs, metadata=item.get_metadata())
168+
result.append(item_new)
169+
else:
170+
img = np.asarray(item.image)
171+
polys = mask_to_polygon(img, mask_threshold=self.mask_threshold, mask_nth=self.mask_nth,
144172
fully_connected=self.fully_connected)
145173
for poly in polys:
146174
px, py = polygon_to_lists(poly, swap_x_y=True, as_type="int")
@@ -151,10 +179,10 @@ def _do_process(self, data):
151179
points = [Point(x, y) for x, y in zip(px, py)]
152180
polygon = Polygon(*points)
153181
obj = LocatedObject(left, top, right - left + 1, bottom - top + 1)
154-
obj.metadata[LABEL_KEY] = label
182+
obj.metadata[LABEL_KEY] = self.label
155183
obj.set_polygon(polygon)
156184
objs.append(obj)
157-
item_new = ObjectDetectionData(source=item.source, image_name=item.image_name, data=item._data,
185+
item_new = ObjectDetectionData(source=item.source, image_name=item.image_name, data=safe_deepcopy(item.data),
158186
annotation=objs, metadata=item.get_metadata())
159187
result.append(item_new)
160188

0 commit comments

Comments
 (0)