java-topology/defects/vtk/unit/SurfaceNetsLabelCollectAlgorithm.java

175 lines
6.5 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
/**
* Models vtkGeneralizedSurfaceNets3D::RequestData() auto-label collection.
*
* When no explicit segmentation labels are provided, the filter collects
* unique region IDs by iterating all numPts scalars:
*
* SLOW: std::find() on a growing std::vector<double> — O(numPts × numLabels)
* FAST: std::unordered_set<double> insertion test — O(numPts)
*
* CWE-407: VTK Filters/Meshing/vtkGeneralizedSurfaceNets3D.cxx:1150
*/
public class SurfaceNetsLabelCollectAlgorithm {
// -------------------------------------------------------------------------
// Slow (defective) implementation.
// -------------------------------------------------------------------------
static class SlowCollect {
long totalOps = 0;
/** Collect unique non-negative region IDs in order of first appearance. */
List<Double> collect(double[] regions) {
List<Double> autoLabels = new ArrayList<>();
for (double regionId : regions) {
if (regionId >= 0) {
boolean found = false;
for (double existing : autoLabels) { // O(k) scan — the defect
totalOps++;
if (existing == regionId) {
found = true;
break;
}
}
if (!found) {
autoLabels.add(regionId);
}
}
}
return autoLabels;
}
}
// -------------------------------------------------------------------------
// Fast (fixed) implementation.
// -------------------------------------------------------------------------
static class FastCollect {
long totalOps = 0;
List<Double> collect(double[] regions) {
HashSet<Double> seen = new HashSet<>();
List<Double> autoLabels = new ArrayList<>();
for (double regionId : regions) {
totalOps++; // one O(1) hash op per point
if (regionId >= 0 && seen.add(regionId)) {
autoLabels.add(regionId);
}
}
Collections.sort(autoLabels); // deterministic ordering
return autoLabels;
}
}
// -------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------
static double[] buildRegions(int numPts, int numLabels) {
double[] regions = new double[numPts];
for (int i = 0; i < numPts; i++) {
regions[i] = i % numLabels; // round-robin label assignment
}
return regions;
}
// -------------------------------------------------------------------------
// Tests
// -------------------------------------------------------------------------
static int passed = 0;
static int total = 0;
static void check(String label, boolean condition) {
total++;
if (condition) {
passed++;
System.out.println(" PASS " + label);
} else {
System.out.println(" FAIL " + label);
}
}
public static void main(String[] args) {
System.out.println("=== SurfaceNetsLabelCollectAlgorithm ===");
// --- Correctness: small known input ---
{
double[] regions = {0, 1, 2, 1, 0, 3, -1, 2, 3};
SlowCollect slow = new SlowCollect();
FastCollect fast = new FastCollect();
List<Double> slowResult = slow.collect(regions);
List<Double> fastResult = fast.collect(regions);
check("small: slow finds 4 labels", slowResult.size() == 4);
check("small: fast finds 4 labels", fastResult.size() == 4);
// Both should contain {0,1,2,3}; fast is sorted
Collections.sort(slowResult);
check("small: results equal after sort", slowResult.equals(fastResult));
}
// --- Correctness: single label ---
{
double[] regions = {5, 5, 5, 5};
SlowCollect slow = new SlowCollect();
FastCollect fast = new FastCollect();
List<Double> sr = slow.collect(regions);
List<Double> fr = fast.collect(regions);
check("single-label: slow size==1", sr.size() == 1);
check("single-label: fast size==1", fr.size() == 1);
check("single-label: value==5.0", fr.get(0) == 5.0);
}
// --- Correctness: all negative (no output labels) ---
{
double[] regions = {-1, -2, -3};
SlowCollect slow = new SlowCollect();
FastCollect fast = new FastCollect();
check("all-neg: slow empty", slow.collect(regions).isEmpty());
check("all-neg: fast empty", fast.collect(regions).isEmpty());
}
// --- Performance: O(numPts × numLabels) vs O(numPts) ---
{
int numPts = 500_000;
int numLabels = 200;
double[] regions = buildRegions(numPts, numLabels);
SlowCollect slow = new SlowCollect();
FastCollect fast = new FastCollect();
long t0 = System.nanoTime();
List<Double> slowResult = slow.collect(regions);
long slowNs = System.nanoTime() - t0;
t0 = System.nanoTime();
List<Double> fastResult = fast.collect(regions);
long fastNs = System.nanoTime() - t0;
// Slow scan count: once every label is seen (after first numLabels pts),
// each subsequent point triggers a full numLabels scan → ≈ numPts × numLabels / 2
long slowOps = slow.totalOps;
long fastOps = fast.totalOps;
double ratio = (double) slowNs / fastNs;
System.out.printf(" INFO numPts=%d numLabels=%d slow_ops=%d fast_ops=%d ratio=%.1fx%n",
numPts, numLabels, slowOps, fastOps, ratio);
check("slow ops >> fast ops (>= 10x)", slowOps >= fastOps * 10);
check("fast ops == numPts", fastOps == numPts);
check("fast is meaningfully faster (>= 3x)", ratio >= 3.0);
check("label counts agree", slowResult.size() == fastResult.size());
}
System.out.println();
System.out.printf("%d/%d PASS%n", passed, total);
if (passed != total) System.exit(1);
}
}