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 — O(numPts × numLabels) * FAST: std::unordered_set 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 collect(double[] regions) { List 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 collect(double[] regions) { HashSet seen = new HashSet<>(); List 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 slowResult = slow.collect(regions); List 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 sr = slow.collect(regions); List 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 slowResult = slow.collect(regions); long slowNs = System.nanoTime() - t0; t0 = System.nanoTime(); List 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); } }