Connected components on 2D and 3D images. Supports multiple labels.
Project description
cc3d: Connected Components on Multilabel 3D Images
Fig. 1. Binary and Multilabel Connected Components Labeling (CCL) 2D images are shown for simplicity. (a) A binary image (foreground white, background black) (b) 4connected CCL of binary image (c) 8connected CCL of binary image (d) A multilabel image (e) 4connected CCL of multilabel image (f) 8connected CCL of multilabel image
Fig. 2. Continuous Value Connected Components Labeling (CCL) (top) A three tone grayscale image with signed additive low magnitude noise (bottom) Extracted components using continuous value CCL with a delta value greater than the noise magnitude but smaller than the difference between tones
cc3d is an implementation of connected components in three dimensions using a 26, 18, or 6connected neighborhood in 3D or 4 and 8connected in 2D. This package uses a 3D variant of the two pass method by Rosenfeld and Pflatz augmented with UnionFind and a decision tree based on the 2D 8connected 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.
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.
In general, binary images are much more common (usually resulting from image thresholding), but multilabel images crop up in instance segmentation and semantic labeling as a classifier may label touching clusters of adjacent pixels differently. If a gap between different labels is guaranteed, then the problem degenerates into the binary version.
Check out benchmarks to see a comparison with SciPy on a few different tasks.
Python pip
Installaction
If compatible binaries are available for your platform, installation is particularly simple.
pip install connectedcomponents3d
If compatible binaries are not available, you can install from source as follows.
Requires a C++ compiler.
pip install numpy
pip install connectedcomponents3d nobinary :all:
Occasionally, you may appear to successfully install cc3d, but on import you'll see an error that includes: numpy.ufunc size changed, may indicate binary incompatibility
. You can either try upgrading numpy or compiling cc3d from source in this case.
Python Manual Installation
Requires a C++ compiler.
pip install r requirements.txt
python setup.py develop
Python Use
The following functions are available with examples below:
 Connected Component Labeling (CCL)
 Removal of small objects ("dust")
 Extraction of k largest objects
 Fast extraction of all objects onebyone
 Calculation of contact surface area and contact network
 Extraction of a per voxel connectivity graph
import cc3d
import numpy as np
labels_in = np.ones((512, 512, 512), dtype=np.int32)
labels_out = cc3d.connected_components(labels_in) # 26connected
connectivity = 6 # only 4,8 (2D) and 26, 18, and 6 (3D) are allowed
labels_out = cc3d.connected_components(labels_in, connectivity=connectivity)
# If you need a particular dtype you can specify np.uint16, np.uint32, or np.uint64
# You can go bigger, not smaller, than the default which is selected
# to be the smallest that can be safely used. This can save you the copy
# operation needed by labels_out.astype(...).
labels_out = cc3d.connected_components(labels_in, out_dtype=np.uint64)
# If you're working with continuously valued images like microscopy
# images you can use cc3d to perform a very rough segmentation.
# If delta = 0, standard high speed processing. If delta > 0, then
# neighbor voxel values <= delta are considered the same component.
# The algorithm can be 210x slower though. Zero is considered
# background and will not join to any other voxel.
labels_out = cc3d.connected_components(labels_in, delta=10)
# If you're working with an image that's larger than memory you can
# use mmapped files. The input and output files can be used independently.
# In this case an array labels.bin that is 5000x5000x2000 voxels and uint32_t
# in Fortran order is computed and the results are written to out.bin in Fortran
# order. You can find the properties of the file (shape, dtype, order) by inspecting
# labels_out.
labels_in = np.memmap("labels.bin", order="F", dtype=np.uint32, shape=(5000, 5000, 2000))
labels_out = cc3d.connected_components(labels_in, out_file="out.bin")
# You can extract the number of labels (which is also the maximum
# label value) like so:
labels_out, N = cc3d.connected_components(labels_in, return_N=True) # free
#  OR 
labels_out = cc3d.connected_components(labels_in)
N = np.max(labels_out) # costs a full read
# You can extract individual components using numpy operators
# This approach is slow, but makes a mutable copy.
for segid in range(1, N+1):
extracted_image = labels_out * (labels_out == segid)
process(extracted_image) # stand in for whatever you'd like to do
# If a readonly image is ok, this approach is MUCH faster
# if the image has many contiguous regions. A random image
# can be slower. binary=True yields binary images instead
# of numbered images.
for label, image in cc3d.each(labels_out, binary=False, in_place=True):
process(image) # stand in for whatever you'd like to do
# Image statistics like voxel counts, bounding boxes, and centroids.
stats = cc3d.statistics(labels_out)
# Remove dust from the input image. Removes objects with
# fewer than `threshold` voxels.
labels_out = cc3d.dust(
labels_in, threshold=100,
connectivity=26, in_place=False
)
# Get a labeling of the k largest objects in the image.
# The output will be relabeled from 1 to N.
labels_out, N = cc3d.largest_k(
labels_in, k=10,
connectivity=26, delta=0,
return_N=True,
)
labels_in *= (labels_out > 0) # to get original labels
# Compute the contact surface area between all labels.
# Only face contacts are counted as edges and corners
# have zero area. To get a simple count of all contacting
# voxels, set `surface_area=False`.
# { (1,2): 16 } aka { (label_1, label_2): contact surface area }
surface_per_contact = cc3d.contacts(
labels_out, connectivity=connectivity,
surface_area=True, anisotropy=(4,4,40)
)
# same as set(surface_per_contact.keys())
edges = cc3d.region_graph(labels_out, connectivity=connectivity)
# You can also generate a voxel connectivty graph that encodes
# which directions are passable from a given voxel as a bitfield.
# This could also be seen as a method of eroding voxels fractionally
# based on their label adjacencies.
# See help(cc3d.voxel_connectivity_graph) for details.
graph = cc3d.voxel_connectivity_graph(labels, connectivity=connectivity)
Note: C and Fortran order arrays will be processed in row major and column major order respectively, so the numbering of labels will be "transposed". The scare quotes are there because the dimensions of the array will not change.
C++ Use
#include "cc3d.hpp"
// 3d array represented as 1d array
int* labels = new int[512*512*512]();
uint32_t* cc_labels = cc3d::connected_components3d<int>(
labels, /*sx=*/512, /*sy=*/512, /*sz=*/512
);
// The default template parameter for output type is uint32_t
uint64_t* cc_labels = cc3d::connected_components3d<int, uint64_t>(
labels, /*sx=*/512, /*sy=*/512, /*sz=*/512
);
uint16_t* cc_labels = cc3d::connected_components3d<int, uint16_t>(
labels, /*sx=*/512, /*sy=*/512, /*sz=*/512,
/*connectivity=*/18 // default is 26 connected
);
size_t N = 0;
uint16_t* cc_labels = cc3d::connected_components3d<int, uint16_t>(
labels, /*sx=*/512, /*sy=*/512, /*sz=*/512,
/*connectivity=*/26, /*N=*/N // writes number of labels to N
);
#include "cc3d_continuous.hpp"
// For handling grayscale images. Note that the difference
// is the addition of the "delta" argument.
uint16_t* cc_labels = cc3d::connected_components3d<int, uint16_t>(
labels, /*sx=*/512, /*sy=*/512, /*sz=*/512,
/*delta=*/10, /*connectivity=*/6 // default is 26 connected
);
#include "cc3d_graphs.hpp"
// edges is [ e11, e12, e21, e22, ... ]
std::vector<uint64_t> edges = cc3d::extract_region_graph<uint64_t>(
labels, /*sx=*/512, /*sy=*/512, /*sz=*/512,
/*connectivity=*/18 // default is 26 connected
);
// graph is a series of bitfields that describe intervoxel
// connectivity based on adjacent labels. See "cc3d_graphs.hpp"
// for details on the bitfield.
uint32_t* graph = extract_voxel_connectivity_graph<T>(
labels, /*sx=*/512, /*sy=*/512, /*sz=*/512,
/*connectivity=*/6 // default is 26 connected
);
26Connected CCL Algorithm
The algorithm contained in this package is an elaboration into 3D images of the 2D image connected components algorithm described by Rosenfeld and Pflatz (RP) in 1968 [1] (which is well illustrated by this youtube video) using an equivalency list implemented as Tarjan's UnionFind disjoint set with path compression and balancing [2] and augmented with a decision tree based on work by Wu, Otoo, and Suzuki (WOS), an approach commonly known as Scan plus Arraybased UnionFind (SAUF). [3] The description below describes the 26connected algorithm, but once you understand it, deriving 18 and 6 are simple. However, we recently made some changes that warrant further discursion on 6connected.
First Principles in 2D
In RP's 4connected twopass method for binary 2D images, the algorithm raster scans and every time it first encounters a foreground pixel (the pixels to its top and left are background), it marks it with a new label. If there is a preexisting label in its neighborhood, it uses that label instead. Whenever two labels are adjacent, it records they are equivalent so that they can be relabeled consistently in the second pass. This equivalency table can be constructed in several ways, but some popular approaches are UnionFind with path compression with balancing by rank and Selkow's algorithm (which can avoid pipeline stalls). [4] However, Selkow's algorithm is designed for two trees of depth two, appropriate for binary images. We would like to process multiple labels at the same time, making UnionFind preferable.
In the second pass, the pixels are relabeled using the equivalency table. UnionFind establishes one label as the root label of a tree, and the root is considered the representative label. Each pixel is then labeled with the representative label. UnionFind is therefore appropriate for representing disjoint sets. Path compression with balancing radically reduces the height of the tree, which accelerates the second pass.
WOS approached the problem of accelerating 8connected 2D connected components on binary images. 8connected labeling is achieved by extending RP's forward pass mask to the top left and top right corner pixels. In UnionFind based connected components algorithms, the unify step in the first pass is the most expensive step. WOS showed how to optimize away a large fraction of these calls using a decision tree that takes advantage of local topology. For example, since the topcenter neighbor of the current pixel is also adjacent to the other mask elements, all of which have already been processed by virtue of the raster scan direction, if it is present it is sufficient to copy its value and move on. If it is absent, pick one of the remaining foreground pixels, copy their value, and use unify for the mask element on the right as it is now known to be nonneighboring with the left hand side. WOS's algorithm continues in this fashion until a match is found or all mask elements are processed at which point a new label is created.
For several years, this algorithm was the world's fastest, though it has been superceded by a newer work that exchanges the static decision tree for a dynamic one or precalculated generated one amongst other improvements. However, WOS's work is significant for both its simplicity and speed and thus serves as the inspiration for this library. For 2D 8connected images, we provide a specialization using Wu et al's original decision tree for a slight performance boost.
We're interested in exploring the block based approaches of Grana, Borghesani, and Cucchiara ([5],[7]), however their approach appears to critically rely on binary images. We'll continue to think about ways to incorporate it. We also considered the approach of He et al [8] which is also supposed to modestly faster than than WOS. However, it substitutes the UnionFind data structure (one array) with three arrays, which imposes a memory requirement that is at odds with our goal of processing large images.
Extending to 3D
The approach presented below is very similar to that of Sutheebanjard [6]. To move to a 3D 26connected neighborhood, the mask must be extended into three dimensions in order to connect neighboring planes. Observe that the 8connected mask covers the trailing half of the neighborhood (the part that will have been already processed) such that the current pixel can rely on those labels. Thus the mask for the 26connected neighborhood covers only two out of three potential planes: the entire lower plane (nine voxels), and a mask identical to WOS's (four voxels) on the current plane. While some further optimizations are possible, to begin, the problem can be conceptually decomposed into two parts: establishing a 9connected link to the bottom plane and then an 8connected link to the current plane. This works because the current pixel functions as a hub that transmits the connection information from the 9connected step to the 8connected step.
Fig. 1: Mask for an 8connected plane. If J,K,L, and M are all eliminated, only N remains and a new label is assigned.
j  k  l 

m  n  . 
.  .  . 
The very first Z plane (Z=0) the algorithm runs against is special: the edge effect omits the bottom plane of the mask. Therefore, as the remaining mask is only comprosed of the 8connected 2D mask, after this pass, the bottom of the image is 8connected. At Z=1, the 9connected part of the mask kicks in, forming connections to Z=0, making the current plane now (8 + 9) 17connected. At Z=2, the 9connected bottom mask now forms connections from Z=1 to Z=2 on the top, making Z=1 (17 + 9) 26connected. By induction, when this process proceeds to completion it results in a 26connected labeling of the volume.
Following inspiration from WOS, we construct a decision tree on the densely labeled bottom plane that minimizes the number of unifications we need to perform.
Fig 2. The mask for the lower plane in 3D.
a  b  c 

d  e  f 
g  h  i 
As e
is connected to all other voxels, if present, it can simply be copied. If e
is absent, b
and h
fully cover the mask. If b
is absent, h
, a
, c
comprise a covering. If h
is absent, b
, g
, i
are one. Below is a list of coverings such that each proceeding entry in the list assumes the first letters in the entries above are background.
e
k
, (h
g
,i
)b
, (h
g
,i
)h
,a
,c
m
, (f
c
,i
)d
, (f
c
,i
)f
,g
,a
a
,c
,g
,i
c
,g
,i
g
,i
i
The decision tree is then constructed such that each of these coverings will be evaluated using the fewest unifications possible. It's possible to further optimize this by noting that e
and b
are both fully connected to the upper 2D mask. Therefore, if either of them are present, we can skip the 8connected unification step. It's also possible to try the DF covering first if B is background, which would save one unification versus HAC given even statistics, but it seems to be slightly slower on the dataset I attempted. To move from binary data to multilabel data, I simply replaced tests for foreground and background with tests for matching labels.
In order to make a reasonably fast implementation, I implemented unionfind with path compression. I conservatively used an IDs array qual to the size of the image for the unionfind data structure instead of a sparse map. The unionfind data structure plus the output labels means the memory consumption will be input + output + rank + equivalences. If your input labels are 32bit, the memory usage will be 4x the input size. This becomes more problematic when 64bit labels are used, but if you know something about your data, you can decrease the size of the unionfind data structure. I previously used unionbysize but for some reason it merely reduced performance and increased memory usage so it was removed.
For more information on the history of connected components algorithms, and an even faster approach for 2D 8connected components, consult Grana et al's paper on Block Based Decision Trees. [5,7]
Phantom Labels
In the course of thinking of improvements to several algorithms, we developed a technique we term "Phantom Labeling" for improving the SAUF method directly.
Definition: Phantom Labels are elements of a CCL mask that
transmit connectivity information between other elements of the
mask but cannot directly pass their value to the current pixel
during the first pass of a SAUF derived algorithm.
Reproducing Fig. 1 again, but with new letters for the more limited problem, the standard SAUF mask appears like so:
Fig. 3: Mask for an 8connected plane.
a  b  c 

d  x  . 
.  .  . 
This results in a decision tree like so assuming x is a foreground pixel.
if b:
x := b
elif a:
x := a
if c:
unify(a,c)
elif d:
x := d
if c:
unify(c,d)
elif c:
x := c
else:
x := new label
There is an opportunity here for eliminating up to half of the unify calls, one of the more expensive operations in modern CCL by slightly modifying the mask:
Fig. 4: 8connected mask modified to include phantom label P.
.  P  . 

a  b  c 
d  x  . 
.  .  . 
This results in a modified decision tree.
if b:
x := b
elif a:
x := a
if c and not P: < change here
unify(a,c)
elif d:
x := d
if c:
unify(c,d)
elif c:
x := c
else:
x := new label
The novelty of this technique is unclear, but it is very simple to apply and results in substantial speed ups for the 4 and 6 connected problems, a minor improvement for 8connected, and is readily compatible with the multilabel approach unlike block based approaches.
4 and 6Connected CCL Algorithm
Here is where the phantom label technique shines. It's a bit harder to find 4 and 6 connected algorithms in the literature, I assume because many of the techniques invented for the 8way problem, such as the UnionFind data structure for the equivalency table and runbased approaches, are applicable to the simpler problem. However, the SAUF decision tree approach was lacking as every pixel required a unify call in the 4way problem and two in the 6way problem.
Fig. 5: 4connected mask modified to include phantom label P.
P  b  . 

a  x  . 
if a:
x := a
if b and not P:
unify(a,b)
elif b:
x := b
else:
x := new label
This gives a decent improvement on the order of 1020%. If you're lucky, you might not incur even a single label merge operation. In the 6way problem, there are three phantom labels that can be exploited and the improvement is closer to 50% on our data, a fairly substantial amount. Again, with luck you might avoid any unify operations at all.
Fig. 6: Mask for the 6way problem with phantom labels P, Q, and R added.
P  b 

a  x 
.  Q 

R  c 
You can even use multiple routes to propagate information if a label is missing. For example, if path (a,P,b) is unavailable due to a missing P, you could potentially transmit information using path (a,R,c,Q,b).
Four Pass Algorithm
We introduce two additional passes over the image label prior to running the twopass SAUF algorithm. These additional passes are used to collect statistcs for optimizing the SAUF passes.
Estimating Provisional Labels
The first additional pass is used to overestimate the number of provisional labels generated by the first SAUF pass. A better estimation allows a smaller allocation for the UnionFind datastructure. For some operating systems, the reduced size of the allocation and improved caching recovers more time than is spent collecting statistics.
This can be computed by counting the number of transitions between labels along each row of the image. This scan is easily written such that the instructions can be vectorized to minimize the cost of the scan. The number of transitions is guaranteed to be larger than or equal to the number of provisional labels as all provisional labels are generated in this fashion and then reduced by stealing a label from a neighboring voxel.
A hierarchy of estimators can be written as:
0 <= provisional labels <= X transitions <= static estimate <= voxels
Binary images can also be estimated statically as voxels / 2
for 4 and 6way, voxels / 4
for 8 and 18 way, and voxels / 8
for 26 connected. For multilabel images, the best static estimate is voxels
as no assumptions can be made about how labels connect to each other (in the worst case all eight voxels in a cube have different labels).
It is also possible to check XY and XYZ transitions to get a tighter bound, but in experiments, the amount of time spent checking those directions exceeded the benefit obtained by checking the X pass. Often the X pass alone results in factors as high as voxels / 100
.
Estimation of the number of labels also allows aborting processing before the first SAUF pass in the case of an all background cube.
Estimating Foreground Location
The second additional pass is estimating the location of the foreground. In the literature, this strategy is sometimes referred to as a "oneandahalf pass" where the foreground location is computed during the first SAUF pass and then used to skip processing of background voxels during the relabeling pass.
Here we perform this check up front so that it can be performed minimally. Instead of integrating the calculation into the first pass which could force some computation on every voxel, we scan each row from the left to find the first foreground voxel and then scan from the right to the find the foreground voxel at the end. The results are tabulated in a uint32 table of starts and ends to each row of size 2 * sy * sz
. This ensures that the volume is scanned at most once, and most likely much less if the shapes fill the space reasonably well. Then, both passes of the SAUF method scan only the part of each row indicated by this table.
Certain shapes and distributions defeat the efficiency of scanning only the starts and ends of the row (such as random images or an image with foreground on the start and end of each row and nowhere else). However, for a great many shapes, this provides substantial efficiencies and minimal downside for a dense multilabel image as only two YZ slices of the images are scanned before the table is completed.
Early Abortion Points
There are three locations in the algorithm at which further processing can be aborted early without changing the result.
 After estimating provisional labels if zero transitions are detected (an all zeros volume). A black image is returned.
 After the first SAUF pass if the number of provisional labels is zero or one. In this case, the provisional labels are guaranteed to be identical to final labels.
 After assigning final labels to each provisional label in a translation array. If the number of final labels equals the number of provisional labels, the provisional labels were accurately assigned and the relabeling scan can be skipped.
Papers Using cc3d
A number of papers are using cc3d now. Many of them seem to be deep learning applications as instance segmentation is liable to generate touching nonbinary labels. Some are in geoscience, neuroscience, and medical fields. If cc3d is helpful to you, please feel free to email us and let us know. We might be able to offer some tips if its performance critical (though we can't guarantee timeliness of response). There are so many variations of the CCL problem, you might be surprised at what you can do.
https://scholar.google.com/scholar?as_ylo=2019&q=connectedcomponents3d&hl=en&as_sdt=0,31
References
 A. Rosenfeld and J. Pfaltz. "Sequential Operations in Digital Picture Processing". Journal of the ACM. Vol. 13, Issue 4, Oct. 1966, Pg. 471494. doi: 10.1145/321356.321357 (link)
 R. E. Tarjan. "Efficiency of a good but not linear set union algorithm". Journal of the ACM, 22:215225, 1975. (link)
 K. Wu, E. Otoo, K. Suzuki. "Two Strategies to Speed up Connected Component Labeling Algorithms". Lawrence Berkeley National Laboratory. LBNL29102, 2005. (link)
 S. Selkow. "The TreetoTree Editing Problem". Information Processing Letters. Vol. 6, No. 6. June 1977. doi: 10.1016/00200190(77)900643 (link)
 C. Grana, D. Borghesani, R. Cucchiara. "Optimized Blockbased Connected Components Labeling with Decision Trees". IEEE Transactions on Image Processing. Vol. 19, Iss. 6. June 2010. doi: 10.1109/TIP.2010.2044963 (link)
 P. Sutheebanjard. "Decision Tree for 3D Connected Components Labeling". Proc. 2012 International Symposium on Information Technology in Medicine and Education. doi: 10.1109/ITiME.2012.6291402 (link)
 C. Grana, D. Borghesani, R. Cucchiara. "Fast Block Based Connected Components Labeling". Proc. 16th IEEE Intl. Conf. on Image Processing. 2009. doi: 10.1109/ICIP.2009.5413731 (link)
 L. He, Y. Chao and K. Suzuki, "A LinearTime TwoScan Labeling Algorithm", IEEE International Conference on Image Processing, vol. 5, pp. 241244, 2007.
Project details
Release history Release notifications  RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Hashes for connectedcomponents3d3.12.0.tar.gz
Algorithm  Hash digest  

SHA256  96e5c04c95dd6c41ec64bfedeaaa1af59b0c23f73e2b583ed061829c3ed57bc6 

MD5  d87392407b381cf7b975c169446e50ee 

BLAKE2b256  a03a8428acb261d7aab53d7677082c2eff119fdff397927e3fefe8331bc38225 
Hashes for connected_components_3d3.12.0cp311cp311win_amd64.whl
Algorithm  Hash digest  

SHA256  e83df4cb2042521af8292bf0826f57ef6973959df9386576b468ff8e1fc60b3f 

MD5  f35bf34d0dd3c064606c510e216c2f50 

BLAKE2b256  2a805655c87796e24d5be5bd2a98c4352b7694e1d25206b2ab42f1bc4ced6e67 
Hashes for connected_components_3d3.12.0cp311cp311win32.whl
Algorithm  Hash digest  

SHA256  c16f0ca0169e6acd0cfbefc39db1bad6958125559312283410f3df4c50282ea6 

MD5  53d851440701f31d147491d3f0943ab0 

BLAKE2b256  46f83987103e958c81b8faf8f65722a6d4cc3c0e0a560f61d64935bd44a23c04 
Hashes for connected_components_3d3.12.0cp311cp311manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm  Hash digest  

SHA256  9eadbe7402fb6d06e97762560e03df1033380f5cf8ca537ac9c2266e266be909 

MD5  829c7824b87fc5ea042d56d2493574d1 

BLAKE2b256  8fd68f0d3c6122ed2947861a6d7743a33dc387a71c4b798d9f0869dd2ab50536 
Hashes for connected_components_3d3.12.0cp311cp311manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm  Hash digest  

SHA256  3efe9f3bc8c4f53d72f097450ee9d8960babc1bc1989ed968e071849b3f4e1ca 

MD5  9627855074395503cfb8b07cd40243c0 

BLAKE2b256  93029088609b0d259be0b482e4a34215d805e1794f14aa26f145de3282a3c2bf 
Hashes for connected_components_3d3.12.0cp311cp311manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm  Hash digest  

SHA256  2bc97543a75d0aaf06de7c6a930efa72e6b86ce6175d011596b04a903a34f4a2 

MD5  d49943bd6a41c149b50f5f56161ad84d 

BLAKE2b256  cd7aeb397600514952d91b036f8bac7099f9db6f78b112914cbf3bfddc45d13c 
Hashes for connected_components_3d3.12.0cp311cp311macosx_10_9_x86_64.whl
Algorithm  Hash digest  

SHA256  2550205d8d262aaec9e333766686b284665901415353e28411be0a06203b4121 

MD5  e349976c071c784cb5344121ed68eced 

BLAKE2b256  78423e99df0f19117f78b2374cff4e33a2b0f83158ef21b4cbf91b763b6eed1a 
Hashes for connected_components_3d3.12.0cp311cp311macosx_10_9_universal2.whl
Algorithm  Hash digest  

SHA256  d23e4e47811bfe3196e73519ee7e4e2e903a67e6e2ab3e4655fcd1433f124e7d 

MD5  51b999584df452c1be173c4bad65ad50 

BLAKE2b256  a9097c9fe93bf5cfd5896bacd257741fa08ff9d8e7617206c83b0c2183f30d10 
Hashes for connected_components_3d3.12.0cp310cp310win_amd64.whl
Algorithm  Hash digest  

SHA256  1d5fc5f10d361c2b7bfe09ad5da6870e310322c8a219fbd180bedb72effccfd8 

MD5  e6e68c0217d8960a6d6ae2852eb831d4 

BLAKE2b256  a83d134e9c48bdaea643d6178e255692448913516cdf9388e75201b1b8282ec1 
Hashes for connected_components_3d3.12.0cp310cp310win32.whl
Algorithm  Hash digest  

SHA256  1c08630cead0762b1a4cabc7893f36c13bf903272fab546306015cb9d26bdb50 

MD5  3a17f952a7543102fd0970f3568f4a76 

BLAKE2b256  842d419f49a329f022025360c115976cdc8fdbb3ad8153a7fe3bb5f4e72c549f 
Hashes for connected_components_3d3.12.0cp310cp310manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm  Hash digest  

SHA256  0edf91bdcac7aabc601f39bb7cdd57f228884f7be0bdb85d1e96c830c4a9fe8c 

MD5  31bfc645aa6423ae916cdad338b6b5e5 

BLAKE2b256  cbb6b6b39ac133553560dffb13c0fa835e5b5066408133802364776a37cce2d7 
Hashes for connected_components_3d3.12.0cp310cp310manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm  Hash digest  

SHA256  8434143c58ea0ffe520471b72e0d747342d047d6e2a6e04820da8d3bafc90e31 

MD5  0aa46203c5ba17020bf414be5e829485 

BLAKE2b256  d642769242ac4ddc9445ed4e3b0d12c542f8e7e603d820e285c9921bfdc213c6 
Hashes for connected_components_3d3.12.0cp310cp310manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm  Hash digest  

SHA256  8bdd8f3236eb9c9b521970a5524282a6522d010d1bb3d030126c098a34aba104 

MD5  458d8b5270492b3878ab4979fad72fa3 

BLAKE2b256  d502398e9f0afda97504fab1fb2166bd3ecc5460073a026faceadfc61b12d075 
Hashes for connected_components_3d3.12.0cp310cp310macosx_10_9_x86_64.whl
Algorithm  Hash digest  

SHA256  9ef0e49c2e16ee2c5eb1d5cc1602c93bb1cdeda1b9b6985473aee0f43ed87b96 

MD5  1d86c7a72df95a3b38ec9a834e6a5c3f 

BLAKE2b256  c033f928819909864605cecfe0ea67bbdad63abe624369f1cf89dcd85caa794b 
Hashes for connected_components_3d3.12.0cp310cp310macosx_10_9_universal2.whl
Algorithm  Hash digest  

SHA256  27c1c1fcde02ce5d16f8bdc79efe7c062cdaff6b0238474961173692db399929 

MD5  dc833b4d2492fcc77e7e7d48c7aa6c0c 

BLAKE2b256  a9973109864cec436e0daaa7a7f8a9139562db2d60dd5fe864bae273194bd323 
Hashes for connected_components_3d3.12.0cp39cp39win_amd64.whl
Algorithm  Hash digest  

SHA256  64589f2a6ce811a9e740f8687091f9c3358f938cf47555d520680522e5174ead 

MD5  af51b6350bdc644094d05ab96f4832cc 

BLAKE2b256  a50f0d8c5bb89daca0875053f3e6972b7ff9cbaa95f7c84d45376a9bbd2c478c 
Hashes for connected_components_3d3.12.0cp39cp39win32.whl
Algorithm  Hash digest  

SHA256  ed1da35821d3ce7727bcf70049ac9d25a91565a034a839877c2aacbe232a8e92 

MD5  e6e98ee902d4590ba391a7cb8cc6a9e0 

BLAKE2b256  dd1cd61a8ab7658d8004630405f22ce500b0bfa23bf478926f070d97c6c3e305 
Hashes for connected_components_3d3.12.0cp39cp39manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm  Hash digest  

SHA256  3a0f4a73bb379a4e20fa98647ef3fc40f1784839b458e5fce9eba711f2ef8304 

MD5  40095a2bb186bd76a8f801fd5641617f 

BLAKE2b256  0325f67fa5a0669e69e8d5badc028b9ce6cc610586af7dfe476df88a245941ad 
Hashes for connected_components_3d3.12.0cp39cp39manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm  Hash digest  

SHA256  4b04d1801e305875d67e3634da0be87a6f6925e948938662a98cfccb8685b0dd 

MD5  cf287a782cdf2fb7499ad8799959bbb7 

BLAKE2b256  5b9813740985224cbbede505d285bff2b5e5a3629145ad3fcd9f343a072b7890 
Hashes for connected_components_3d3.12.0cp39cp39manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm  Hash digest  

SHA256  b774b4dcb3e27480f25bcee9cd01ae663739131bdaa4ff27f614689418f14aed 

MD5  9fe9c77c27298d55beae6839239537ff 

BLAKE2b256  a48c1cd805e05cc128fbee7d0cb343d9615ef8f87a36fb680b11decbd9e608a7 
Hashes for connected_components_3d3.12.0cp39cp39macosx_10_9_x86_64.whl
Algorithm  Hash digest  

SHA256  0564bf742d1b8a44489ced1b84a0f856747bb52e12c6f39bbee4acdcd1d02f7b 

MD5  5947fec0a5d4a1fae2349a5dba60bd7d 

BLAKE2b256  855b90b258b3e66254cde431b7fef8184dcf07111e490054c3528de0f48f9f87 
Hashes for connected_components_3d3.12.0cp39cp39macosx_10_9_universal2.whl
Algorithm  Hash digest  

SHA256  f5877b9101ee6866e223ea1e4fab53f15aecf36b33e4e2196af2889b39da2264 

MD5  6c53bbfb6ef661231e542770d1e7b46c 

BLAKE2b256  378848ad21c977b673cef804c85a8830cb9508813f0d9cc4c074d6ae737788e5 
Hashes for connected_components_3d3.12.0cp38cp38win_amd64.whl
Algorithm  Hash digest  

SHA256  c3054dcd588ffd058d58daca55e202686c71e72347ceaa10e7479a61944c0a2e 

MD5  683c870d50c376ced524d540d6c5d94a 

BLAKE2b256  49acd3b85c32ca7205a1381ab26a26a623e9a33ee1f562b9747d0201f83dbffc 
Hashes for connected_components_3d3.12.0cp38cp38win32.whl
Algorithm  Hash digest  

SHA256  c0aafd0d34662ea5e1895fede46a22a635dba0afa65ace3c238ee5b7119c0790 

MD5  4953a181742ceaa1557bc5900abe6cb7 

BLAKE2b256  07050884d31b155eb0df89d896e567695994800c1cffc1b741f9082b1ae84751 
Hashes for connected_components_3d3.12.0cp38cp38manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm  Hash digest  

SHA256  1c5d72012402499145592b8498f5e5059d165d2570fcccc084dea53353057f89 

MD5  1570a33964239db6c7cc11b56e1d379f 

BLAKE2b256  c6823ba47a0ed2a614f96a301a71ce049d9163a6aef9519f473f3b4e3d64877b 
Hashes for connected_components_3d3.12.0cp38cp38manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm  Hash digest  

SHA256  03e3db297085e85396d72080b17f0d8a078c6179a3b937cb6539d8ffef2ac6ff 

MD5  2f242334860a347dd36df1d87cce9e95 

BLAKE2b256  e94464583c98234b80100bec8f90c6a1a72888cd3fae801ff90b457e8fdd47cb 
Hashes for connected_components_3d3.12.0cp38cp38manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm  Hash digest  

SHA256  2dae038203fa6b6dca86602717e52510c2daea34f0c795690af37a8d4b09d322 

MD5  8a3ab39aa38099da6ae5da754e5b83ac 

BLAKE2b256  2806f34b4035e7fa11e8e5282697bd92bad917ccd09ab0658221e310f283889b 
Hashes for connected_components_3d3.12.0cp38cp38macosx_11_0_universal2.whl
Algorithm  Hash digest  

SHA256  a282603558df0586ffa8ece6f08bce02ffce307435667f5f8cfe2df6fce8beda 

MD5  e504c9570caa25b5831e132a416465cd 

BLAKE2b256  f49bc667239e2ddb5522650b5c37ce4125ac1ca3b42b14423d914ee2a232b805 
Hashes for connected_components_3d3.12.0cp38cp38macosx_10_9_x86_64.whl
Algorithm  Hash digest  

SHA256  1db19f8f2c0825aa97c78c0ae071e8d9350b36915ae6627d01598cee67ab94ce 

MD5  c17835f9e67f7ee42761f95d46174fd1 

BLAKE2b256  586098324a4b8f1ff65adf0a23f0a00239fe1cfc1fd635eb3e4474a8b4b0f661 
Hashes for connected_components_3d3.12.0cp37cp37mwin_amd64.whl
Algorithm  Hash digest  

SHA256  3e51ea280f89401cdf9fd290a353e129c1184a8db071614a60fd8bccb3f76b0c 

MD5  0688bf00d899e9fea0b17038581ae486 

BLAKE2b256  83df9da371242ca253e1f4ee0380cf5c8ea70acc4e96ba3f344e0ad0689d2e57 
Hashes for connected_components_3d3.12.0cp37cp37mwin32.whl
Algorithm  Hash digest  

SHA256  82b95c4bda925079004fd50aaaeaa56cb4b14d4931e77a36106d64e349416f89 

MD5  5e368ddc6eb85e99cdd207cf6ed33535 

BLAKE2b256  913e436d912cd84894e648b5ae7020fa507730e243a7f8d480016c77918666b5 
Hashes for connected_components_3d3.12.0cp37cp37mmanylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm  Hash digest  

SHA256  4c8b5826b086dd2a44c5f23ddc0af92aeb8d8ba90a41bfb9e27fd05c3d275dc7 

MD5  16085c3260ae8f75720fd67182206b81 

BLAKE2b256  ac3e1fc2eaeb264baf97a41039c9dda80f108ac42db2d746aed6054fb77b80b7 
Hashes for connected_components_3d3.12.0cp37cp37mmanylinux_2_17_i686.manylinux2014_i686.whl
Algorithm  Hash digest  

SHA256  4e785bf9701faa626bc623a8ebada46f3f688a05c073d98fb872b2b061166ea9 

MD5  064aa272b8d3873b861f8d0cb08f1ab3 

BLAKE2b256  3ffcbaeb6617c22b7b555ff7a208d47c20af507f4c9771f3f7f96d4ebc91163d 
Hashes for connected_components_3d3.12.0cp37cp37mmanylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm  Hash digest  

SHA256  f50479c1b2f004ee649b553ec1fb4f7dd54f540794829baad70407b05f2c9819 

MD5  6dda6450709c8d9a4b0406ec7154d38e 

BLAKE2b256  85522db956ebf9b3ff2efce9c79a75fc11fc5ba8774a36e3a827c253897a6476 
Hashes for connected_components_3d3.12.0cp37cp37mmacosx_10_9_x86_64.whl
Algorithm  Hash digest  

SHA256  08a605dbc32079b0ab9b76ba2e144f15d0208b3332356911ae39b4e029fa6fc4 

MD5  6bb423a9b7210e010396a547e9f6a227 

BLAKE2b256  7c7f8496b8ad8798af4ad09b41fe057b89b5d70dd10762840dd1f4fce8972b82 