Connected components on discrete and continuous multilabel 3D and 2D images. Handles 26, 18, and 6 connected variants; periodic boundaries (4, 8, & 6).
Project description
cc3d: Connected Components on Multilabel 3D Images
Fig. 1. Binary and Multilabel Connected Components Labeling (CCL) 2D images are shown for simplicity. Black is the background color (zero). (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 the borders to wrap around (e.g. for simulations, world maps)
# specify periodic_boundary=True, currently only supported for
# 4 and 8 (2d) and 6 (3d) connectivities.
labels_out = cc3d.connected_components(
labels_in, connectivity=connectivity, periodic_boundary=True
)
# 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.14.1.tar.gz
Algorithm  Hash digest  

SHA256  fdc4099508b734b266a42ee12534ee42e29467c28506b137e8c1edc8c7513c92 

MD5  2855745f05fff3e4ad29d02d845559d5 

BLAKE2b256  b822641778d39d352538bea91e7d437e1e88763f7c34a921efafa41dca48b5a6 
Hashes for connected_components_3d3.14.1cp312cp312win_amd64.whl
Algorithm  Hash digest  

SHA256  69dadde8c3f2a05c09c144d8a4d6c4df6684582c6d8aaf7552fd2b499e9284f6 

MD5  bbbaf67fa76afc3a91585e5a5758392b 

BLAKE2b256  e09307a49719ba5dee730b07c82612b728e49f325c7ef82b9fc8ed984754fc24 
Hashes for connected_components_3d3.14.1cp312cp312win32.whl
Algorithm  Hash digest  

SHA256  0fc3358b665c53d91fd4970fe39e3d0520ba96ef4f35d68a33a117aecae77054 

MD5  b16934f161012a5277e9ebea375a2601 

BLAKE2b256  204582b463ef0e5e050f480026823c900f1f5ffd55d59407585f8f0ee2fbec3a 
Hashes for connected_components_3d3.14.1cp312cp312manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm  Hash digest  

SHA256  c3babc21d6f5ac6879a85627469f54f131508f15f1ddb50123fed8d2f40e1582 

MD5  dad2f481a0351f6f5132a9056604f2c0 

BLAKE2b256  80e2a51b865553c2bdcab197c31d5b6d353a5b56e24ec636837f4155ea07dc9d 
Hashes for connected_components_3d3.14.1cp312cp312manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm  Hash digest  

SHA256  da35340591cea8f7aae55f9d7b3c0d77d41bc58eed7958fc7ff51cf2f5f28b39 

MD5  02804d8d05d42a79634706cfe8a3c2ba 

BLAKE2b256  36301edcf7dcbf1a555ff157803304b6e874aff7e3b6772480c88aa622701e4a 
Hashes for connected_components_3d3.14.1cp312cp312macosx_11_0_arm64.whl
Algorithm  Hash digest  

SHA256  bffc8d5441b22994f4b19a56affe8265d06eadcf32f10357f589582c7dca2ff8 

MD5  e4b1553e9076d0f1a2de65a67a87efc8 

BLAKE2b256  dbae30023694cdf85d118b177c7571c0bd4f9e9da3aa4a7cc0b352abd0f2b426 
Hashes for connected_components_3d3.14.1cp312cp312macosx_10_9_x86_64.whl
Algorithm  Hash digest  

SHA256  1e176548ed7eb2350a5822da7a76f60cf9d6d806a9340d91423c4d05d9ba0561 

MD5  ed1424544d1a55736aeffbb98e6271a9 

BLAKE2b256  74a2c2b2a7abbecd2f57b9618d7e01ba34f5f93109d23b41250d6717821e8dbe 
Hashes for connected_components_3d3.14.1cp311cp311win_amd64.whl
Algorithm  Hash digest  

SHA256  97606afaa927df4d561cef89c0182078f8eaf7b54baebfbc57012c1c75db0a8d 

MD5  2f355e862b2c038deefa77b353358438 

BLAKE2b256  6556cda1aaa4accf092461c499f24ccc44527730baa98f1f70f9ed1e5efb2486 
Hashes for connected_components_3d3.14.1cp311cp311win32.whl
Algorithm  Hash digest  

SHA256  00e39d5908e0b1874b1fe10fa1fe90993a778ff003e908730372c773fa156b13 

MD5  747dcbd21fb09e423a6fcea5ab465424 

BLAKE2b256  257179730e7104a14f22d717141b735597b6b5aed0e32afb57341045f5d26ff8 
Hashes for connected_components_3d3.14.1cp311cp311manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm  Hash digest  

SHA256  74e60d38922d25d49e6b850a396396644fcc30b2cd238d2625138d3e0fb9eae1 

MD5  9021f45838646360b8d90ff3e820d610 

BLAKE2b256  c67c1e50a9f9773faf749b7316be3654ef64b8273f16bc9dc41e15316a4f24d7 
Hashes for connected_components_3d3.14.1cp311cp311manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm  Hash digest  

SHA256  f7c7575430d24d80b85d4fff93a952883583129bf371b263f9b7db4cbbd22c11 

MD5  c896ac24cb524537c50a336e9008dbb0 

BLAKE2b256  9df4066899795319fa225a8ba7ce990c7172b82703e5e18b79d295a3dde56747 
Hashes for connected_components_3d3.14.1cp311cp311manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm  Hash digest  

SHA256  a3e3d79b090ce57af7135b4472607efd710d79131dcb6e0c48d1aa9b4938ca75 

MD5  359fe5826b0e66e3a06832ae9e3d4ed1 

BLAKE2b256  91f56720865b8d0b5136ba76d25c0f9fee6f6f96cc3b05c61196a89721d76c9e 
Hashes for connected_components_3d3.14.1cp311cp311macosx_11_0_arm64.whl
Algorithm  Hash digest  

SHA256  dc730b8870de65ca38a2b1096ddd9f5f1dd8b7c0067fd33efe98d1a461ef0af2 

MD5  ec78dde1987a58e6fd4c2a34e17a2f4f 

BLAKE2b256  1be5d9817544589c89680afbb8438a6906a7be56492929c24c95523d304ca115 
Hashes for connected_components_3d3.14.1cp311cp311macosx_10_9_x86_64.whl
Algorithm  Hash digest  

SHA256  fcad396d8ae80e687ee9921f15477aa4c88d9e95546d11123633fcbf3067e015 

MD5  0d66e09eaf36469a589aedb353e8950e 

BLAKE2b256  7764455dc0fa95aa8515409f53b3e7c7b85117081f1c11e5f751bd569aca80d7 
Hashes for connected_components_3d3.14.1cp310cp310win_amd64.whl
Algorithm  Hash digest  

SHA256  b753d6e78c965d1c74e7760543ac900331b987c6708f3a5cec55fd8052dda055 

MD5  2365d0b228cc245d97dd97641f5cd8d6 

BLAKE2b256  979f44d7b5c216e4da3cf805bf24e5ba49d7bc5d9419e3bbf2089537f08b09c4 
Hashes for connected_components_3d3.14.1cp310cp310win32.whl
Algorithm  Hash digest  

SHA256  6e04d3b8e8ff109a501579d3a0915ee13890dbdfc1615d5047bda0a44ca4cf55 

MD5  332c6f6418b5269f9555ea9777f4f9b5 

BLAKE2b256  8f3ed1eb489fa13b471d7eb5b6f07d816e5389d3083a1e17cf5c2ecf19d95ed1 
Hashes for connected_components_3d3.14.1cp310cp310manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm  Hash digest  

SHA256  d43442efd212891664df6fe2f6197a907b5ce58ca36794f7da7c4a2669ceea3e 

MD5  d6392039bd2341abef5c638c7b955923 

BLAKE2b256  6e8fdba00ed6411503fcf8bc7f715552ca802090ac23290361b13b850f8dca3f 
Hashes for connected_components_3d3.14.1cp310cp310manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm  Hash digest  

SHA256  b720148eba532513a04df08c8b6c80b14161ce794fafd35b9fc9d2b7a2efdd2c 

MD5  d8578abf99016013acbf929508b3e97d 

BLAKE2b256  68e62268daf9b9f7c85f102e6daf6c67108c29df4a459a0e6d425f73b2d31511 
Hashes for connected_components_3d3.14.1cp310cp310manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm  Hash digest  

SHA256  eda25c50a1471adf6ccd07928101103aa0c3cc92066cfcea2e1b0bccfe7d5fec 

MD5  ebe34a3155d3b4032897da60be7ca401 

BLAKE2b256  69bcd3c68fa4a2e01c60128d0d4839e7bbedffb197b8bcea630598629676b6e3 
Hashes for connected_components_3d3.14.1cp310cp310macosx_11_0_arm64.whl
Algorithm  Hash digest  

SHA256  bd38180beb4b58ea6c2c5e1a09aac3f79363f08decf2e54fc1b769884149b984 

MD5  14b9ad3b98da3836cb14d60b49fd8595 

BLAKE2b256  56a643ef5f326bc4ffb7a879ff4b75e932a27282c865c336903b70d68e5858ce 
Hashes for connected_components_3d3.14.1cp310cp310macosx_10_9_x86_64.whl
Algorithm  Hash digest  

SHA256  4f660af0a8337216eb404319535041d417fb5a1af83a40ad45b0b08d492db8ce 

MD5  eb545b9c9eb7ce3573ef645899ed8217 

BLAKE2b256  ba93dd4be1cb8d7cc252a917f98c7c9278f408e47fc7e85dd91aad50c3c681d5 
Hashes for connected_components_3d3.14.1cp39cp39win_amd64.whl
Algorithm  Hash digest  

SHA256  6e018a3feaa89ae1ede7d0e3ec6de885b16f6092b6f310c2d2c8129c65b7c07f 

MD5  a08fd54e0b76c1bda8e2358eacaefcb2 

BLAKE2b256  1ea45ac26f52445dabe9d06a840126b2f3acd0e635cbb702d94a03993009d66f 
Hashes for connected_components_3d3.14.1cp39cp39win32.whl
Algorithm  Hash digest  

SHA256  95d46c10cb47ee80c37734b559829a90409e7d37cefae6722d9046df55a90430 

MD5  ebec6425d5e74333a4198b61de585c85 

BLAKE2b256  1ddd51e2b8618b52717e69485080daee79a6c50e8bf333c19f3c9889eb0cd4c3 
Hashes for connected_components_3d3.14.1cp39cp39manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm  Hash digest  

SHA256  4f772bb3179167e1bd3114bc1bc7221089bdbfc59fb1571f4adcab77583a2378 

MD5  1590d22bb4e59e0f01e6894c444cb61a 

BLAKE2b256  d5c44cd8a3026986b3a2f1eff940085220aee6ee9beb73e312170629ff73c2f1 
Hashes for connected_components_3d3.14.1cp39cp39manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm  Hash digest  

SHA256  7ef79e391142c72fab7ca92835ca51521b80bb4ae33b57cb12072b285cdb5b1b 

MD5  6767d049827d92b51a499fc0d2815619 

BLAKE2b256  4b2f59772b43e4736d6e4f9a0a3e1c19967e763e36095eb7307ff29c3978da85 
Hashes for connected_components_3d3.14.1cp39cp39manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm  Hash digest  

SHA256  ba16a0a42668fbd058f09cd2fbdcb5644ea26e109cf88fcfa01ee4805f09dd5b 

MD5  1b0cbebd9c82ff3b3cabab810765c43f 

BLAKE2b256  ee897dfcdb92edd08ec40fd310867cb0132901a4e2e86a67fbbacd5b56034dfd 
Hashes for connected_components_3d3.14.1cp39cp39macosx_11_0_arm64.whl
Algorithm  Hash digest  

SHA256  b9197ab7dfe3cbc5c5224a8c038b50f9a94f95fb0effe0d3f10b69860d4fd41c 

MD5  e6e7874555504490516ae19276ce3fb2 

BLAKE2b256  d0489cc5d5ff0d225e2a3a81c9e1a8265bf429fe5e5f136e20e7c23cc4a1d25e 
Hashes for connected_components_3d3.14.1cp39cp39macosx_10_9_x86_64.whl
Algorithm  Hash digest  

SHA256  e3038c9b27b16d6a1c3d8526555b54d8a7ed6bfb96b08589e24bc60437dda9de 

MD5  215737ab5533e14e57ecc162d02bb591 

BLAKE2b256  ad5af6c0c5a2241c6d5b293581c9005a41e7096a5be0e198563fa0ac687e5b4e 
Hashes for connected_components_3d3.14.1cp38cp38win_amd64.whl
Algorithm  Hash digest  

SHA256  dabfa536bb913925624c2aad5a0bc46cffea5ff8325bc086f170bdc89724033b 

MD5  bc2203c798e26ec9c040195fd1372005 

BLAKE2b256  cae80d2b8acad1c62ec41877b5379b8adaa14157561ef70dd9b7b75fbe9e108b 
Hashes for connected_components_3d3.14.1cp38cp38win32.whl
Algorithm  Hash digest  

SHA256  c2913e87851ffeaf69ef2a58d7ad7e59b95217c2e5b6c10cc87049b32228a257 

MD5  fa8f064969420fe1bea2edddf43eec07 

BLAKE2b256  a147b26af3f7f4b65a25039069d525c4adbab6513c0d829fce825f80f8e558a6 
Hashes for connected_components_3d3.14.1cp38cp38manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm  Hash digest  

SHA256  af67ecb7ebfb0d7c7eaf6998c12484fe40487e1268202320d56329e170887cab 

MD5  a790d26f120665ce989a0dac51a957d6 

BLAKE2b256  f681bf628182c89c3a1dda1fa9317272ddc72dcfc81c3030db0db87b40864be9 
Hashes for connected_components_3d3.14.1cp38cp38manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm  Hash digest  

SHA256  2c4935656f420802f2b47a5213901bfc8b723ec26b25f1f64f551a7c412d790e 

MD5  3eb4bb8275a755d3ac4c36fbe3fdfd1c 

BLAKE2b256  5ea8262eb61907df924b1cba80fb32cd50898094177053a468a880ec23e278e6 
Hashes for connected_components_3d3.14.1cp38cp38manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm  Hash digest  

SHA256  14f14fb3d4dfd401954d9a18131ad25f6dee0ece83f2cf0e077f21ac54b5d218 

MD5  e5e1b36997252badd1b8cb9c6fae5ffb 

BLAKE2b256  e8938971238daeb3808becc84ee4c9b28c3258c48a50cc38297802dfc54bf571 
Hashes for connected_components_3d3.14.1cp38cp38macosx_11_0_arm64.whl
Algorithm  Hash digest  

SHA256  527cc73d901126d85e5cc18613b6a47187a533c55902f2cc535d9a8f5affdb44 

MD5  5d0008fe8d4e864474d39e8a9aab650b 

BLAKE2b256  3ec26c959256c59094e9df716391bde4901c0f05892453dceb20f21baf71aae7 
Hashes for connected_components_3d3.14.1cp38cp38macosx_10_9_x86_64.whl
Algorithm  Hash digest  

SHA256  650268fc2584073a6e57053758a75cab40e67d5196233cc183c9a2e3c1ba47d4 

MD5  fceb748c6b6e48c064069ee0da183f90 

BLAKE2b256  a7644ecb4437cbcaf174694ce6d1c8963c2dd8a0bca2aa321ef354fe20542bc6 
Hashes for connected_components_3d3.14.1cp37cp37mwin_amd64.whl
Algorithm  Hash digest  

SHA256  07d282a847404fcaaf5353eda0dcb25971656db30f5f5886cad0289f87b4d8c0 

MD5  9dc9fb484a3ec056ba472c7dfcef3220 

BLAKE2b256  ea6b6ee8cef6b0b46601a14000c07a3a366ddddaf6f2a2a335cfc6d868668aca 
Hashes for connected_components_3d3.14.1cp37cp37mwin32.whl
Algorithm  Hash digest  

SHA256  97a3ce0b17ed01c9451620005382886065de390c7ec7f2dd55eca2818613412b 

MD5  3c98ceef1223a9b4fd6efc2b6d077405 

BLAKE2b256  d13bb811759b57908aa144ed92ffa867f3c6bd5a7cf9bef31540267a66609498 
Hashes for connected_components_3d3.14.1cp37cp37mmanylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm  Hash digest  

SHA256  6d10095bc68c92db273d6dfe7fcbb6d4e67db3f288a1d983e70af3c274abfaca 

MD5  3eff51883ef63e9ab82b68d6f706146f 

BLAKE2b256  8b1b47e3e899d76481f41438e2d12f915bb2510cad4c070c0c83e6eff1292523 
Hashes for connected_components_3d3.14.1cp37cp37mmanylinux_2_17_i686.manylinux2014_i686.whl
Algorithm  Hash digest  

SHA256  4e7a0a8731f29070f161dd1642e746660588eb6a594164a0f51325a93addbe89 

MD5  37db84d8460e23fa1591736cb3932ab1 

BLAKE2b256  4bed78bbe13dcdbc0f9f4af9b0c5b2cfc6de1972251e8c2b8a9c24e249767575 
Hashes for connected_components_3d3.14.1cp37cp37mmanylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm  Hash digest  

SHA256  0dc0718240c204c5a62bd602b166cffb577f9b55aca016ff15ac79f47eeadd39 

MD5  8b3d2d5d1ec612f9486fcadb38d4da38 

BLAKE2b256  cc34e4c20850fe78a6a378be8488e5cf45c5d203733b9e4b5762335c32ca3987 
Hashes for connected_components_3d3.14.1cp37cp37mmacosx_10_9_x86_64.whl
Algorithm  Hash digest  

SHA256  fc203dd1dbe1c1b0299bc6f86e4689c9cc159f66ca9057fed0a427b55e6ca2d5 

MD5  204333e49798b9d55ffd31909701197e 

BLAKE2b256  630009cb25027c96ea8592859a389c79a0e183186b27be3e232c0061af14ad8b 