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
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) # 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.10.0.tar.gz
Algorithm  Hash digest  

SHA256  0615fad1aea23d5e0fa3a272035e8b8d9fd85021c2e3a773f1e750fd1e040899 

MD5  8f8ebca33cc15b182b4ceeccf998033d 

BLAKE2256  f936c55df2528eb0393c04a611f46352f16a5b175a0251b3559644c010a2fdc9 
Hashes for connected_components_3d3.10.0cp310cp310win_amd64.whl
Algorithm  Hash digest  

SHA256  2ac2ae11f89fadad7262267fb66979e5172c6e32de94c0875d47b018731b78a3 

MD5  ed08c7242f525ee728edcf2a7e8a226f 

BLAKE2256  cd1f31e8b4bfdb03730d14317b564256dc6bf44be2906afd41b6d96e703b19ff 
Hashes for connected_components_3d3.10.0cp310cp310win32.whl
Algorithm  Hash digest  

SHA256  a623a3dad9552097387685d9964dc6bd3a2e1dcea000acd6af65f9f560b78fff 

MD5  8354dc68ac0beb39f415ec3ca45e8529 

BLAKE2256  48fe3cf30c9cabe96cecfcbb8a1bfd3981d514d962fa2fdc6d0d0b95385f2eb3 
Hashes for connected_components_3d3.10.0cp310cp310manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm  Hash digest  

SHA256  7334be99d7cbc99cbc9f56037579607a1a0b5e8eb84057177f5367894f23f3ee 

MD5  178d4a23746d209b31841cab00432d95 

BLAKE2256  f1e6c344e688b6c79f52897e44974b43a851ba74cb00dc7d11d10ac9ddb15fb0 
Hashes for connected_components_3d3.10.0cp310cp310manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm  Hash digest  

SHA256  21784560ef6b8424b112f2e0dcfcbf58cc0a2fae67e3d1d9264d1ca02255e17d 

MD5  ae8264f0197d1e75bb1b5ac815b14bd5 

BLAKE2256  4e8f3d8cea3fd015ae0ae1fc74516cf2ed4730b55ca9dc0e8aa82698a75a1cc7 
Hashes for connected_components_3d3.10.0cp310cp310manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm  Hash digest  

SHA256  95f2f902ce63556ae5127c6f9db80a250b198877093ab364c3c3a87067b42097 

MD5  e8132f67556bb7b230dc0e4f97649e90 

BLAKE2256  e84d0f3187e20d20854562b1b9ecfb29e5c9cc30566e871788b1c66f70cc542e 
Hashes for connected_components_3d3.10.0cp310cp310macosx_10_9_x86_64.whl
Algorithm  Hash digest  

SHA256  71123b2947174a45b56e3c36f56f21b999aab56672ef0c152b566bc6a936a141 

MD5  b33676cc2ccc3a4bd6109f4bd433b2e8 

BLAKE2256  be21e5c30fafe90de5293dea27c027aedf0edd5771b86b41d37f440ac6473477 
Hashes for connected_components_3d3.10.0cp310cp310macosx_10_9_universal2.whl
Algorithm  Hash digest  

SHA256  439bc03f3cd2c1e958877371a2275a33256dda5848c9ca02fad6b1ef6fc0a26a 

MD5  bc68ef1aa68743ab0687d883c181d05f 

BLAKE2256  70e0b61c37d66609782bc0cd54144437b3b076fb35587fb31e9eb999be02353f 
Hashes for connected_components_3d3.10.0cp39cp39win_amd64.whl
Algorithm  Hash digest  

SHA256  61153c17c45908011e6d8412f8b6f890af49f936eaad7b687d07122c1d70d3bc 

MD5  b15b1b4a24be1afd6608a1a9df0c9392 

BLAKE2256  2fd1b5e2d64add8aea1ff3adcc045a70273fa993398bc46fdec9930e28c7757e 
Hashes for connected_components_3d3.10.0cp39cp39win32.whl
Algorithm  Hash digest  

SHA256  a33b364a6c73b7dcb0ab8209d2a041a4ff7e88ac5ad2c00d54a099622805fb27 

MD5  c6392c335cdc4f5cfbc3a63b9f7f2038 

BLAKE2256  057dada73f6df2a64aed5e72c7f9c2e64f56917557b25e04090a7d388bd52646 
Hashes for connected_components_3d3.10.0cp39cp39manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm  Hash digest  

SHA256  608ebbc9ac78d59650d5d118a190397b5e6dd0e87624a8fea1bbe15486344271 

MD5  93edb1cea731f16847f18eb0299b7650 

BLAKE2256  19f1b0f999f7ac9d835f9d73d193884e5cc25890e385d76bacc6492ae51d4a6b 
Hashes for connected_components_3d3.10.0cp39cp39manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm  Hash digest  

SHA256  391dc4017d4754dfe685f8e584c0c3d3fc77da8f5799376a3c13d6b2e3c0d023 

MD5  e3303a0a05bc622a703dde2963df9d5f 

BLAKE2256  621c2a38f98d40531c852725236a91ba53a082cdfd0da14e204018c8e567b59c 
Hashes for connected_components_3d3.10.0cp39cp39manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm  Hash digest  

SHA256  afdb6209473bad3ff35d850ac2a36a27e93e9c620d9ccdc7fad3ae65fc51a820 

MD5  50966a069a06c258b2d22c9fd8319b87 

BLAKE2256  64e7591b83a365920c2f48356501bb3767887e929d11ebbfd361be5d2519a6c1 
Hashes for connected_components_3d3.10.0cp39cp39macosx_10_9_x86_64.whl
Algorithm  Hash digest  

SHA256  8cec1c7bb971771d0a10566b5286d0684a53ff0eb563bf6af122d15d6d49e4cc 

MD5  4bd81c8baf07a62973c8ea32d128faa8 

BLAKE2256  5a36b5f4cc1120116fe91cd49402a0ed5a9fdb64e9d8b4961141c4e1f4204650 
Hashes for connected_components_3d3.10.0cp39cp39macosx_10_9_universal2.whl
Algorithm  Hash digest  

SHA256  4fb5ddf5e8b3927d1fefa6470d2398db576d37a12980b8cf82d1477346104caa 

MD5  9f0e8005352f0073c9d42798d4fd8f07 

BLAKE2256  5dfa3f40960d544e0cd30cc9275f70592512f3ac4225d0e4aeb5cb88be4f0aa1 
Hashes for connected_components_3d3.10.0cp38cp38win_amd64.whl
Algorithm  Hash digest  

SHA256  de8ca62f20c1e5df737db0ca07a47bf10da965929926555eeeed42f9e58ab109 

MD5  dcf12f49bd361c88d6dfce5ddba1d06c 

BLAKE2256  0756008369e3d7c5ec3746a3f78254118861d1f06ceadc955e7f4c852c199220 
Hashes for connected_components_3d3.10.0cp38cp38win32.whl
Algorithm  Hash digest  

SHA256  8d9d5bb3e30c2a0e4642d1ce0997e88d1821b286fd7edd6ad234615797950184 

MD5  a671aa15c90ead0539eb46e82ffabcde 

BLAKE2256  6ab05e2b75ff22334bc8da1e5ba60dfd73180036cba2cd3f6b4a3cd3171215c8 
Hashes for connected_components_3d3.10.0cp38cp38manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm  Hash digest  

SHA256  37fecf439b16fc85433eee188b56c674c18da1614bd052c63b822405f84a11ed 

MD5  3ef96def5027e795233b1869e74eee1d 

BLAKE2256  3912ea7bb42746787fe2df4e4ede224d04c91405d8a63df2671ed5955eaea7ec 
Hashes for connected_components_3d3.10.0cp38cp38manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm  Hash digest  

SHA256  be18028f0f12e970d5972c2cdab124d244f840b0df3dd807d05e2c1e50637605 

MD5  8e5ba7d12c146e6fbe020d6433bbad3f 

BLAKE2256  9f6545b4009a39101ca47f4fbafabb55bd85482e5bdb166b1004a332572cf28e 
Hashes for connected_components_3d3.10.0cp38cp38manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm  Hash digest  

SHA256  f10e752b4049bc926f0cfb59d3a1c27b5a919d69300501db12fae3b25c9f2512 

MD5  47ec47fa8c4c7d54874a90e222931b84 

BLAKE2256  7a4fed02dfd6892de1b4eea3b563facb31c6629890dfa4bca03cfbc94d8ab5cd 
Hashes for connected_components_3d3.10.0cp38cp38macosx_11_0_universal2.whl
Algorithm  Hash digest  

SHA256  01348292fd8f5d74669d9058c5f567ee90bc59d31e5620d3dda9b4de62b07777 

MD5  1b57ff0a89e12ff4482e341dc4c5dba8 

BLAKE2256  c55fffe066fd8021a4739bd43464c08ae26a206c324477bcbdcfe08a3161d68f 
Hashes for connected_components_3d3.10.0cp38cp38macosx_10_9_x86_64.whl
Algorithm  Hash digest  

SHA256  6f475c99b49c8e6965921ed5a2dd67145f2056c5c5b1e1752d443da727877c88 

MD5  c5c834ed34260f07ab04b1c042f13210 

BLAKE2256  098cbd0e0b9a8190c6959d2010625061ca0aac5a8a592b77f779a00e4ab86fe1 
Hashes for connected_components_3d3.10.0cp37cp37mwin_amd64.whl
Algorithm  Hash digest  

SHA256  d4fdabd4d2aee195eb33c241f991c04d04009c7c07ff839ae4e6410891f7617d 

MD5  495adc65ff6327528d7eeba480e04002 

BLAKE2256  d02897d995655ee1bcdd7617898868568c04b569f0162fcbf6026bde473bbe4c 
Hashes for connected_components_3d3.10.0cp37cp37mwin32.whl
Algorithm  Hash digest  

SHA256  f8134bfd2de687c296f8436682a16a8122108f4969e0a9d92a745c28fe903587 

MD5  0fb52814983ba89f86ec6d6caa9c435a 

BLAKE2256  1cb4cead0dfe4323cddee7b1f3598bcd2d3ecba452b68fff26b9b5edf8dd4d4c 
Hashes for connected_components_3d3.10.0cp37cp37mmanylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm  Hash digest  

SHA256  74a79d3b9b66b974a8ec94f18547abc32be5ac3e46e4e8d1fbc5e39e2088936f 

MD5  b18a5c0b07a4b9e3694dcc6fa3aca67b 

BLAKE2256  77863821ae644e16aae6204b89ab2d588aa1ebb4d46ea152f8bf1928cfe64f05 
Hashes for connected_components_3d3.10.0cp37cp37mmanylinux_2_17_i686.manylinux2014_i686.whl
Algorithm  Hash digest  

SHA256  5f4ba68f49a241012fc133cbdecf9dafe6219bb472bb92789aaafde042eecc86 

MD5  92300abf16ba2ddc0676f1de115d3e96 

BLAKE2256  b2cf3296cf320269814f91e3d5f3c6dbaf2a6f3559dea07e83b5ff9c84f19f15 
Hashes for connected_components_3d3.10.0cp37cp37mmanylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm  Hash digest  

SHA256  d0f55698b1363f21a910d90db943268b8034e708f1de71a5f6aca853c509cb0e 

MD5  c1b48e72769d5e4ea195960d92943f29 

BLAKE2256  cec3d14a560143776a42a26df2dac43ee303b0472508ad246df13d0d4ac8034d 
Hashes for connected_components_3d3.10.0cp37cp37mmacosx_10_9_x86_64.whl
Algorithm  Hash digest  

SHA256  8cac03d99245ccfc3895193029ba0343d4a3e2058eb86814c8914d00fbe46ed9 

MD5  2df578683d21ace710b803c3665a3cfe 

BLAKE2256  0c34c736de11f7064514288e170a8e19ca68bc16e7469c8685f703244d024fb6 