You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Rewrite the Algorithm section to fully teach the filter:
* State what the three output arrays (NumElements, Volume,
EquivalentDiameter) represent and show the spherical/circular
diameter formulas
* Image Geometry path: explain the uniform-voxel-volume shortcut
that lets the filter skip per-voxel volume computations, then
walk the 256K-tuple chunked count pass and the per-feature
output pass; cover the 2D fallback rules and the
two-empty-dimensions preflight error
* RectGrid path: contrast with the Image case, describe the
lockstep FeatureIds + elementSizes chunked read, and explain
why Kahan summation is needed to avoid float32 rounding error
on billion-voxel volumes
* Justify the 256K chunk size choice based on HDF5 chunk-lookup
overhead vs. L2 cache residency
* Summarize memory footprint
Signed-off-by: Joey Kleingers <joey.kleingers@bluequartz.net>
Copy file name to clipboardExpand all lines: src/Plugins/SimplnxCore/docs/ComputeFeatureSizesFilter.md
+50-4Lines changed: 50 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,13 +18,59 @@ During the computation of the **Feature** sizes, the size of each individual **E
18
18
19
19
## Algorithm
20
20
21
-
For **Image Geometry**, the algorithm counts voxels per feature, then multiplies by the uniform voxel volume (product of spacings) to obtain volumes. For 2D image geometries (one dimension equals 1), areas and equivalent circular diameters are computed instead.
21
+
### What the filter computes
22
22
23
-
For**Rectilinear Grid Geometry**, where each cell can have a different volume, the algorithm reads both the Feature IDs and element sizes in lockstep and uses Kahan summation to accurately accumulate per-feature volumes.
23
+
Each**Feature** occupies some number of **Cells** (voxels) in the input grid. The filter produces three arrays indexed by Feature ID:
24
24
25
-
### Performance
25
+
-**NumElements** — the number of voxels belonging to each feature (int32).
26
+
-**Volume** (or **Area** in 2D) — the physical volume (area) of each feature (float32).
27
+
-**EquivalentDiameter** — the diameter of the sphere (or circle in 2D) that would have the same volume (area) as the feature (float32).
26
28
27
-
This filter is optimized for out-of-core (OOC) data storage. The Feature IDs array (and element sizes for Rectilinear Grid) is read in fixed-size chunks (64K tuples) via `copyIntoBuffer()`. Per-feature voxel counts and volumes are accumulated in plain `std::vector` buffers. This chunked approach reduces the number of OOC I/O operations from O(total_voxels) to O(total_voxels / 64K).
29
+
Equivalent spherical diameter `d` is computed from volume `V` using `V = (4π/3)·r³`, giving `d = 2·(V / (4π/3))^(1/3)`. The 2D equivalent circular diameter uses `A = π·r²`, giving `d = 2·(A/π)^(1/2)`.
30
+
31
+
### Image Geometry path
32
+
33
+
An **Image Geometry** has uniform voxel spacing, so the volume of every voxel is the same: `V_voxel = dx · dy · dz`. The filter exploits this to skip per-voxel volume computations:
34
+
35
+
1.**Count voxels per feature.** Stream the per-cell Feature IDs array in 256K-tuple chunks. For each chunk:
36
+
- Bulk-read the chunk via `copyIntoBuffer()` into a 1 MB RAM buffer.
37
+
- Loop over the buffer, incrementing `featureVoxelCounts[id]` for each voxel's Feature ID.
38
+
- The counter array is indexed by feature (thousands of entries, ~8 bytes each) and easily fits in L2 cache, so increments cost a few cycles.
39
+
2.**Compute per-feature outputs** in a single pass over the feature-level arrays:
A 2D fallback (when any one of the three dimensions equals 1) computes area and **Equivalent Circular Diameter** using `2·sqrt(Area/π)` instead. The filter errors out in preflight if two or more dimensions equal 1, because the intended orientation for the 1D/2D scaling is ambiguous in that case.
45
+
46
+
### Rectilinear Grid Geometry path
47
+
48
+
A **Rectilinear Grid** has per-voxel spacing, so cell volumes vary. The filter can't use the uniform-spacing shortcut and must sum each feature's member voxel volumes explicitly:
49
+
50
+
1.**Cache element sizes** via the geometry's `findElementSizes()` helper (stored as a cell-level array).
51
+
2.**Count voxels and sum volumes in lockstep.** Stream both the Feature IDs array and the element sizes array in 256K-tuple chunks (~2 MB working set total):
52
+
- Bulk-read matching chunks of Feature IDs and element sizes.
53
+
- For each voxel in the chunks:
54
+
- Increment `featureVoxelCounts[id]`.
55
+
- Accumulate `featureVolumes[id] += elementSize[i]` using **Kahan summation** (tracks per-feature compensator terms to correct floating-point rounding error). Kahan is necessary because summing billions of float32 volumes in native precision would lose low-order bits, especially for large features.
56
+
3.**Compute per-feature outputs** using `featureVolumes[f]` directly (no post-multiplication).
57
+
58
+
If `Save Element Sizes` is off, the element sizes array created in step 1 is deleted at the end to save memory.
59
+
60
+
### Why 256K chunks
61
+
62
+
The voxel-counting pass is I/O-bound on OOC-backed Feature IDs. Each `copyIntoBuffer()` call carries fixed HDF5 chunk-lookup overhead; the compute (a single indexed increment) is memory-bandwidth-bound on a counter that easily fits in L2. On a 2 B-voxel volume, 256 K-tuple chunks reduce the call count from ~30 K (at the old 64 K chunk) to ~7.5 K while keeping per-chunk memory at a bounded 1 MB. Larger chunks yield diminishing returns because they no longer align with any HDF5 chunk shape.
63
+
64
+
### Memory footprint
65
+
66
+
Peak working memory:
67
+
68
+
-`featureVoxelCounts` — `numFeatures × 8 B` (thousands of features → tens of KB).
69
+
-`featureVolumes`, `featureCompensators` — same size (RectGrid only).
70
+
- Feature IDs chunk buffer — 1 MB.
71
+
- Element sizes chunk buffer — 1 MB (RectGrid only).
72
+
73
+
All feature-level allocations are O(features), which is inherently small (thousands); chunk buffers are O(1) in dataset size. Billion-voxel volumes run without pressure on RAM.
0 commit comments