Skip to content

Commit 881038d

Browse files
TeranisCopilot
andcommitted
feat: update rp factory to use own find obj function
Co-authored-by: Copilot <copilot@github.com>
1 parent 2b5fe62 commit 881038d

4 files changed

Lines changed: 72 additions & 53 deletions

File tree

.github/workflows/build_cython_extensions.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ name: Build Cython extensions
22

33
on:
44
push:
5-
branches:
6-
- main
7-
paths:
5+
paths:
86
- "cellacdc/**/*.pyx"
97
- "setup.py"
108

cellacdc/precompiled/__init__.py

Whitespace-only changes.

cellacdc/regionprops.py

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
_normalize_spacing,
77
)
88
import traceback as traceback
9+
10+
try:
11+
from .precompiled.regionprops_helper import find_all_objects_2D, find_all_objects_3D
12+
_CYTHON_FIND_OBJECTS = True
13+
except Exception:
14+
_CYTHON_FIND_OBJECTS = False
915
# WARNING: Developers have already used
1016
# 7 hrs
1117
# to optimize this.
@@ -55,23 +61,36 @@ def _acdc_regionprops_factory(
5561
)
5662

5763
regions = []
58-
objects = ndi.find_objects(label_image)
59-
for i, sl in enumerate(objects, start=1):
60-
if sl is None:
61-
continue
62-
63-
regions.append(
64-
acdcRegionProperties(
65-
sl,
66-
i,
67-
label_image,
68-
intensity_image,
69-
cache,
70-
spacing=spacing,
71-
extra_properties=extra_properties,
64+
if _CYTHON_FIND_OBJECTS:
65+
img_uint32 = label_image.astype(np.uint32, copy=False)
66+
if label_image.ndim == 2:
67+
objects = find_all_objects_2D(img_uint32)
68+
for label, (r0, r1, c0, c1) in objects:
69+
sl = (slice(r0, r1), slice(c0, c1))
70+
regions.append(acdcRegionProperties(
71+
sl, label, label_image, intensity_image, cache,
72+
spacing=spacing, extra_properties=extra_properties,
73+
offset=offset_arr,
74+
))
75+
else:
76+
objects = find_all_objects_3D(img_uint32)
77+
for label, (z0, z1, r0, r1, c0, c1) in objects:
78+
sl = (slice(z0, z1), slice(r0, r1), slice(c0, c1))
79+
regions.append(acdcRegionProperties(
80+
sl, label, label_image, intensity_image, cache,
81+
spacing=spacing, extra_properties=extra_properties,
82+
offset=offset_arr,
83+
))
84+
else:
85+
objects = ndi.find_objects(label_image)
86+
for i, sl in enumerate(objects, start=1):
87+
if sl is None:
88+
continue
89+
regions.append(acdcRegionProperties(
90+
sl, i, label_image, intensity_image, cache,
91+
spacing=spacing, extra_properties=extra_properties,
7292
offset=offset_arr,
73-
)
74-
)
93+
))
7594
return regions
7695

7796

cellacdc/regionprops_helper.pyx

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
# cython: boundscheck=False, wraparound=False, cdivision=True
33
import numpy as np
44
cimport numpy as np
5-
from libc.limits cimport INT_MAX
5+
from libc.limits cimport UINT_MAX
66

7-
def find_all_objects_2D(np.int32_t[:, :] label_img):
8-
cdef int n_rows = label_img.shape[0]
9-
cdef int n_cols = label_img.shape[1]
10-
cdef int i, j, label, max_label = 0
7+
def find_all_objects_2D(np.uint32_t[:, :] label_img):
8+
cdef Py_ssize_t n_rows = label_img.shape[0]
9+
cdef Py_ssize_t n_cols = label_img.shape[1]
10+
cdef Py_ssize_t i, j
11+
cdef unsigned int label, max_label = 0
1112

1213
# First pass: find max label to allocate C arrays
1314
for i in range(n_rows):
@@ -19,34 +20,35 @@ def find_all_objects_2D(np.int32_t[:, :] label_img):
1920
if max_label == 0:
2021
return []
2122

22-
cdef np.ndarray[np.int32_t, ndim=1] _rs = np.full(max_label + 1, INT_MAX, dtype=np.int32)
23-
cdef np.ndarray[np.int32_t, ndim=1] _re = np.full(max_label + 1, -1, dtype=np.int32)
24-
cdef np.ndarray[np.int32_t, ndim=1] _cs = np.full(max_label + 1, INT_MAX, dtype=np.int32)
25-
cdef np.ndarray[np.int32_t, ndim=1] _ce = np.full(max_label + 1, -1, dtype=np.int32)
23+
cdef np.ndarray[np.uint32_t, ndim=1] _rs = np.full(max_label + 1, UINT_MAX, dtype=np.uint32)
24+
cdef np.ndarray[np.uint32_t, ndim=1] _re = np.zeros(max_label + 1, dtype=np.uint32)
25+
cdef np.ndarray[np.uint32_t, ndim=1] _cs = np.full(max_label + 1, UINT_MAX, dtype=np.uint32)
26+
cdef np.ndarray[np.uint32_t, ndim=1] _ce = np.zeros(max_label + 1, dtype=np.uint32)
2627

27-
cdef int[:] rs = _rs, re = _re, cs = _cs, ce = _ce
28+
cdef unsigned int[:] rs = _rs, re = _re, cs = _cs, ce = _ce
2829

2930
# Second pass: compute bounding boxes without Python objects in the hot loop
3031
for i in range(n_rows):
3132
for j in range(n_cols):
3233
label = label_img[i, j]
3334
if label > 0:
34-
if i < rs[label]: rs[label] = i
35-
if i + 1 > re[label]: re[label] = i + 1
36-
if j < cs[label]: cs[label] = j
37-
if j + 1 > ce[label]: ce[label] = j + 1
35+
if i < rs[label]: rs[label] = <unsigned int>i
36+
if i + 1 > re[label]: re[label] = <unsigned int>(i + 1)
37+
if j < cs[label]: cs[label] = <unsigned int>j
38+
if j + 1 > ce[label]: ce[label] = <unsigned int>(j + 1)
3839

3940
return [
4041
(lbl, (rs[lbl], re[lbl], cs[lbl], ce[lbl]))
4142
for lbl in range(1, max_label + 1)
42-
if re[lbl] != -1
43+
if re[lbl] != 0
4344
]
4445

45-
def find_all_objects_3D(np.int32_t[:, :, :] label_img):
46-
cdef int n_z = label_img.shape[0]
47-
cdef int n_rows = label_img.shape[1]
48-
cdef int n_cols = label_img.shape[2]
49-
cdef int i, j, k, label, max_label = 0
46+
def find_all_objects_3D(np.uint32_t[:, :, :] label_img):
47+
cdef Py_ssize_t n_z = label_img.shape[0]
48+
cdef Py_ssize_t n_rows = label_img.shape[1]
49+
cdef Py_ssize_t n_cols = label_img.shape[2]
50+
cdef Py_ssize_t i, j, k
51+
cdef unsigned int label, max_label = 0
5052

5153
# First pass: find max label
5254
for i in range(n_z):
@@ -59,30 +61,30 @@ def find_all_objects_3D(np.int32_t[:, :, :] label_img):
5961
if max_label == 0:
6062
return []
6163

62-
cdef np.ndarray[np.int32_t, ndim=1] _zs = np.full(max_label + 1, INT_MAX, dtype=np.int32)
63-
cdef np.ndarray[np.int32_t, ndim=1] _ze = np.full(max_label + 1, -1, dtype=np.int32)
64-
cdef np.ndarray[np.int32_t, ndim=1] _rs = np.full(max_label + 1, INT_MAX, dtype=np.int32)
65-
cdef np.ndarray[np.int32_t, ndim=1] _re = np.full(max_label + 1, -1, dtype=np.int32)
66-
cdef np.ndarray[np.int32_t, ndim=1] _cs = np.full(max_label + 1, INT_MAX, dtype=np.int32)
67-
cdef np.ndarray[np.int32_t, ndim=1] _ce = np.full(max_label + 1, -1, dtype=np.int32)
64+
cdef np.ndarray[np.uint32_t, ndim=1] _zs = np.full(max_label + 1, UINT_MAX, dtype=np.uint32)
65+
cdef np.ndarray[np.uint32_t, ndim=1] _ze = np.zeros(max_label + 1, dtype=np.uint32)
66+
cdef np.ndarray[np.uint32_t, ndim=1] _rs = np.full(max_label + 1, UINT_MAX, dtype=np.uint32)
67+
cdef np.ndarray[np.uint32_t, ndim=1] _re = np.zeros(max_label + 1, dtype=np.uint32)
68+
cdef np.ndarray[np.uint32_t, ndim=1] _cs = np.full(max_label + 1, UINT_MAX, dtype=np.uint32)
69+
cdef np.ndarray[np.uint32_t, ndim=1] _ce = np.zeros(max_label + 1, dtype=np.uint32)
6870

69-
cdef int[:] zs = _zs, ze = _ze, rs = _rs, re = _re, cs = _cs, ce = _ce
71+
cdef unsigned int[:] zs = _zs, ze = _ze, rs = _rs, re = _re, cs = _cs, ce = _ce
7072

7173
# Second pass: compute bounding boxes
7274
for i in range(n_z):
7375
for j in range(n_rows):
7476
for k in range(n_cols):
7577
label = label_img[i, j, k]
7678
if label > 0:
77-
if i < zs[label]: zs[label] = i
78-
if i + 1 > ze[label]: ze[label] = i + 1
79-
if j < rs[label]: rs[label] = j
80-
if j + 1 > re[label]: re[label] = j + 1
81-
if k < cs[label]: cs[label] = k
82-
if k + 1 > ce[label]: ce[label] = k + 1
79+
if i < zs[label]: zs[label] = <unsigned int>i
80+
if i + 1 > ze[label]: ze[label] = <unsigned int>(i + 1)
81+
if j < rs[label]: rs[label] = <unsigned int>j
82+
if j + 1 > re[label]: re[label] = <unsigned int>(j + 1)
83+
if k < cs[label]: cs[label] = <unsigned int>k
84+
if k + 1 > ce[label]: ce[label] = <unsigned int>(k + 1)
8385

8486
return [
8587
(lbl, (zs[lbl], ze[lbl], rs[lbl], re[lbl], cs[lbl], ce[lbl]))
8688
for lbl in range(1, max_label + 1)
87-
if ze[lbl] != -1
89+
if ze[lbl] != 0
8890
]

0 commit comments

Comments
 (0)