0
# Statistics and Analysis
1
2
Comprehensive analysis functions for extracting detailed information about connected components, including geometric properties, component extraction, and efficient iteration over individual regions.
3
4
## Capabilities
5
6
### Component Statistics
7
8
Compute detailed statistics about each connected component including voxel counts, bounding boxes, and centroids with options for different output formats.
9
10
```python { .api }
11
def statistics(
12
out_labels: NDArray[Any],
13
no_slice_conversion: bool = False,
14
) -> Union[StatisticsDict, StatisticsSlicesDict]:
15
"""
16
Compute basic statistics on the regions in the image.
17
18
Parameters:
19
- out_labels: A numpy array of connected component labels
20
- no_slice_conversion: If True, return bounding_boxes as numpy array instead of slice tuples
21
22
Returns:
23
- StatisticsDict: With bounding_boxes as list of slice tuples
24
- StatisticsSlicesDict: With bounding_boxes as numpy array (if no_slice_conversion=True)
25
26
Dictionary structure:
27
{
28
'voxel_counts': NDArray[np.uint32], # Index is label, value is voxel count
29
'bounding_boxes': NDArray[np.uint16] | list[tuple[slice, slice, slice]],
30
'centroids': NDArray[np.float64], # Shape (N+1, 3) for x,y,z coordinates
31
}
32
"""
33
```
34
35
Usage examples:
36
37
```python
38
import cc3d
39
import numpy as np
40
41
# Generate connected components
42
labels_in = np.random.randint(0, 10, (100, 100, 100))
43
labels_out = cc3d.connected_components(labels_in)
44
45
# Get statistics (default format with slice tuples)
46
stats = cc3d.statistics(labels_out)
47
48
# Access component information
49
num_components = len(stats['voxel_counts']) - 1 # Subtract 1 for background
50
print(f"Found {num_components} components")
51
52
# Get size of each component (skip background at index 0)
53
component_sizes = stats['voxel_counts'][1:]
54
print(f"Sizes: {component_sizes}")
55
56
# Get centroid coordinates
57
centroids = stats['centroids'][1:] # Skip background
58
for i, centroid in enumerate(centroids):
59
x, y, z = centroid
60
print(f"Component {i+1} centroid: ({x:.2f}, {y:.2f}, {z:.2f})")
61
62
# Extract regions using bounding boxes
63
for label in range(1, len(stats['voxel_counts'])):
64
bbox_slices = stats['bounding_boxes'][label]
65
region = labels_out[bbox_slices]
66
component_mask = (region == label)
67
print(f"Component {label} bounding box shape: {region.shape}")
68
69
# Alternative format with numpy arrays for bounding boxes
70
stats_array = cc3d.statistics(labels_out, no_slice_conversion=True)
71
# bounding_boxes is now NDArray[np.uint16] with shape (N+1, 6)
72
# Structure: [xmin, xmax, ymin, ymax, zmin, zmax] for each component
73
bbox_array = stats_array['bounding_boxes']
74
for label in range(1, len(stats_array['voxel_counts'])):
75
xmin, xmax, ymin, ymax, zmin, zmax = bbox_array[label]
76
print(f"Component {label}: x=[{xmin}:{xmax}], y=[{ymin}:{ymax}], z=[{zmin}:{zmax}]")
77
```
78
79
### Component Iteration
80
81
Efficiently iterate through individual connected components with options for binary or labeled output and memory-optimized processing.
82
83
```python { .api }
84
def each(
85
labels: NDArray[UnsignedIntegerT],
86
binary: bool = False,
87
in_place: bool = False,
88
) -> Iterator[tuple[int, NDArray[UnsignedIntegerT]]]:
89
"""
90
Returns an iterator that extracts each label from a dense labeling.
91
92
Parameters:
93
- labels: Connected component labeled array
94
- binary: Create binary images (True/False) instead of using original label values
95
- in_place: Much faster but resulting images are read-only
96
97
Returns:
98
- Iterator yielding (label, image) tuples
99
100
Yields:
101
- label: int, the component label
102
- image: NDArray, the extracted component (binary or labeled based on 'binary' parameter)
103
"""
104
```
105
106
Usage examples:
107
108
```python
109
import cc3d
110
import numpy as np
111
112
# Create test data
113
labels_in = np.random.randint(0, 5, (50, 50, 50))
114
labels_out = cc3d.connected_components(labels_in)
115
116
# Iterate through components (memory-efficient, read-only)
117
for label, component_image in cc3d.each(labels_out, in_place=True):
118
component_volume = np.sum(component_image > 0)
119
print(f"Component {label}: {component_volume} voxels")
120
121
# Process the component (read-only access)
122
max_value = np.max(component_image)
123
print(f" Max value: {max_value}")
124
125
# Create binary masks for each component
126
for label, binary_mask in cc3d.each(labels_out, binary=True, in_place=False):
127
# binary_mask contains only True/False values
128
print(f"Component {label} binary mask sum: {np.sum(binary_mask)}")
129
130
# Since in_place=False, we can modify the mask
131
binary_mask[binary_mask] = 255 # Convert to grayscale
132
133
# Create labeled images for each component (mutable copies)
134
for label, labeled_image in cc3d.each(labels_out, binary=False, in_place=False):
135
# labeled_image contains the original label values for this component
136
# Can be modified since in_place=False
137
labeled_image[labeled_image == label] = 1000 # Relabel component
138
139
# Compare performance approaches
140
# Fast but read-only (recommended for analysis)
141
for label, img in cc3d.each(labels_out, in_place=True):
142
analyze_component(img) # Read-only analysis function
143
144
# Slower but mutable (for modification)
145
for label, img in cc3d.each(labels_out, in_place=False):
146
modify_component(img) # Function that modifies the component
147
```
148
149
### Low-Level Component Access
150
151
Direct access to component location data for advanced processing and custom rendering operations.
152
153
```python { .api }
154
def runs(labels: NDArray[np.unsignedinteger]) -> dict[int, list[tuple[int, int]]]:
155
"""
156
Returns a dictionary describing where each label is located.
157
158
Parameters:
159
- labels: Connected component labeled array
160
161
Returns:
162
- dict mapping label -> list of (start, end) run positions
163
"""
164
165
def draw(
166
label: ArrayLike,
167
runs: list[tuple[int, int]],
168
image: NDArray[UnsignedIntegerT],
169
) -> NDArray[UnsignedIntegerT]:
170
"""
171
Draws label onto the provided image according to runs.
172
173
Parameters:
174
- label: The label value to draw
175
- runs: List of (start, end) positions from runs() function
176
- image: Target image to draw into
177
178
Returns:
179
- Modified image with label drawn
180
"""
181
```
182
183
Usage examples:
184
185
```python
186
import cc3d
187
import numpy as np
188
189
# Get component locations as run-length encoded data
190
labels_out = cc3d.connected_components(input_image)
191
component_runs = cc3d.runs(labels_out)
192
193
# Examine structure
194
for label, run_list in component_runs.items():
195
if label == 0: # Skip background
196
continue
197
print(f"Component {label} has {len(run_list)} runs")
198
print(f" First run: positions {run_list[0][0]} to {run_list[0][1]}")
199
200
# Reconstruct individual components using runs
201
empty_image = np.zeros_like(labels_out)
202
for component_label in range(1, 6): # Components 1-5
203
if component_label in component_runs:
204
cc3d.draw(component_label, component_runs[component_label], empty_image)
205
206
# Create custom visualization by selectively drawing components
207
visualization = np.zeros_like(labels_out)
208
large_components = [label for label, runs in component_runs.items()
209
if len(runs) > 100] # Components with many runs
210
for label in large_components:
211
cc3d.draw(255, component_runs[label], visualization) # Draw in white
212
```