Skip to content

Commit 07fd876

Browse files
feat: Continuous value CCL for direct application to microscopy images (#80)
* wip: experimenting with continuous value CCL on 4 & 8 connected * feat: add in wip implementation of 6,18,26 connected * fix: missing allocating provisional labels * fix: remove debug statement * chore: add new file to MANIFEST.in * test: demonstrate that continuous values minimally works * feat: add support for float and double input types * fix: compilation error on MSVC * test: add tests for floats and doubles * docs: show how to use delta mode * docs: show how to use delta * docs: make delta visible in function sig * test: add a randomized test to see if more voxels are joined
1 parent 7978f96 commit 07fd876

File tree

7 files changed

+5768
-4116
lines changed

7 files changed

+5768
-4116
lines changed

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
include cc3d.hpp
22
include cc3d_graphs.hpp
3+
include cc3d_continuous.hpp
34
include LICENSE

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
cc3d: Connected Components on Multilabel 3D Images
44
=======================
55

6-
Implementation of connected components in three dimensions using a 26, 18, or 6 connected neighborhood in 3D or 4 and 8-connected in 2D. This package uses a 3D variant of the two pass method by Rosenfeld and Pflatz augmented with Union-Find and a decision tree based on the 2D 8-connected work of Wu, Otoo, and Suzuki. This implementation is compatible with images containing many different labels, not just binary images. It can be used with 2D or 3D images.
6+
Implementation of connected components in three dimensions using a 26, 18, or 6 connected neighborhood in 3D or 4 and 8-connected in 2D. This package uses a 3D variant of the two pass method by Rosenfeld and Pflatz augmented with Union-Find and a decision tree based on the 2D 8-connected work of Wu, Otoo, and Suzuki. This implementation is compatible with images containing many different labels, not just binary images. It also supports continuously valued images such as grayscale microscope images with an algorithm that joins together nearby values. It can be used with 2D or 3D images.
77

88
I wrote this package because I was working on densely labeled 3D biomedical images of brain tissue (e.g. 512x512x512 voxels). Other off the shelf implementations I reviewed were limited to binary images. This rendered these other packages too slow for my use case as it required masking each label and running the connected components algorithm once each time. For reference, there are often between hundreds to thousands of labels in a given volume. The benefit of this package is that it labels all connected components in one shot, improving performance by one or more orders of magnitude.
99

@@ -51,6 +51,13 @@ labels_out = cc3d.connected_components(labels_in) # 26-connected
5151
connectivity = 6 # only 4,8 (2D) and 26, 18, and 6 (3D) are allowed
5252
labels_out = cc3d.connected_components(labels_in, connectivity=connectivity)
5353

54+
# If you're working with continuously valued images like microscopy
55+
# images you can use cc3d to perform a very rough segmentation.
56+
# If delta = 0, standard high speed processing. If delta > 0, then
57+
# neighbor voxel values <= delta are considered the same component.
58+
# The algorithm can be 2-10x slower though.
59+
labels_out = cc3d.connected_components(labels_in, delta=10)
60+
5461
# You can extract the number of labels (which is also the maximum
5562
# label value) like so:
5663
labels_out, N = cc3d.connected_components(labels_in, return_N=True) # free

automated_test.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
TEST_TYPES = [
88
np.int8, np.int16, np.int32, np.int64,
99
np.uint8, np.uint16, np.uint32, np.uint64,
10+
np.float32, np.float64
1011
]
1112

1213
OUT_TYPES = [ np.uint16, np.uint32, np.uint64 ]
@@ -848,3 +849,69 @@ def test_statistics(order):
848849
"centroids": None
849850
}
850851

852+
@pytest.mark.parametrize("connectivity", (8, 18, 26))
853+
@pytest.mark.parametrize("dtype", TEST_TYPES)
854+
@pytest.mark.parametrize("order", ("C", "F"))
855+
def test_continuous_ccl_diagonal(order, dtype, connectivity):
856+
labels = np.zeros((2,2), dtype=dtype, order=order)
857+
labels[0,0] = 1
858+
labels[1,0] = 2
859+
labels[0,1] = 3
860+
labels[1,1] = 4
861+
862+
out = cc3d.connected_components(labels, delta=0, connectivity=connectivity)
863+
assert np.all(np.unique(labels) == [1,2,3,4])
864+
865+
out = cc3d.connected_components(labels, delta=1, connectivity=connectivity)
866+
assert np.all(out == 1)
867+
868+
@pytest.mark.parametrize("connectivity", (4, 6))
869+
@pytest.mark.parametrize("dtype", TEST_TYPES)
870+
@pytest.mark.parametrize("order", ("C", "F"))
871+
def test_continuous_ccl_4_6(order, dtype, connectivity):
872+
labels = np.zeros((2,2), dtype=dtype, order=order)
873+
labels[0,0] = 1
874+
labels[1,0] = 2
875+
labels[0,1] = 3
876+
labels[1,1] = 4
877+
878+
out = cc3d.connected_components(labels, delta=0, connectivity=connectivity)
879+
assert np.all(np.unique(labels) == [1,2,3,4])
880+
881+
out = cc3d.connected_components(labels, delta=1, connectivity=connectivity)
882+
assert np.all(out == np.array([
883+
[1, 2],
884+
[1, 2],
885+
]))
886+
887+
@pytest.mark.parametrize("dtype", TEST_TYPES)
888+
@pytest.mark.parametrize("connectivity", (4, 8))
889+
@pytest.mark.parametrize("order", ("C", "F"))
890+
def test_continuous_blocks(dtype, connectivity, order):
891+
mask = np.random.randint(0,5, size=(64,64)).astype(dtype)
892+
img = np.zeros((512,512), dtype=dtype)
893+
img[64:128, 64:128] = 50 + mask
894+
img[200:264, 64:128] = 70 + mask
895+
896+
img = np.ascontiguousarray(img)
897+
if order == "F":
898+
img = np.asfortranarray(img)
899+
900+
out = cc3d.connected_components(
901+
img, connectivity=connectivity, delta=0
902+
)
903+
assert np.unique(out).size > 1000
904+
905+
out = cc3d.connected_components(
906+
img, connectivity=connectivity, delta=1
907+
)
908+
assert np.unique(out).size > 3
909+
910+
out = cc3d.connected_components(
911+
img, connectivity=connectivity, delta=5
912+
)
913+
assert np.all(np.unique(out)== [0,1,2])
914+
915+
916+
917+

0 commit comments

Comments
 (0)