src.tests.taut_string_reclassifier_test
Classes
- class src.tests.taut_string_reclassifier_test.TautStringReclassifierTest
- Author:
Alberto M. Esmoris Pena
Taut string reclassifier test that exercises the full M1..M6 stack on a battery of synthetic walls. The test covers the core 15-case scene matrix under both operating modes (A: ground-based ambiguity breaker; B: non-ambiguity-resolution), four dedicated edge cases (lower-ground-median, lower-ground-base, inconclusive fallback, near-horizontal skip), and five cross-cutting scenarios (thread-determinism, eager-validation rejections, output-feature inclusion flags, empty / degenerate inputs, INFO logging contract).
The synthetic scenes are generated on the fly: a flat wall or a quadric polynomial wall, optionally with one or more analytical underhang / overhang deviations of known depth and footprint, and optionally with a ground patch (mode A) on the outward side of the wall. The expected ground-truth labels and per-point depth values are derived analytically and asserted against the algorithm’s output.
The run method chains every sub-method through a boolean and-chain; the test passes only when every assertion holds.
- Variables:
BASE_INPUT_CLASS_NAMES (list of str) – Canonical input class names used by every spec.
"wall"is the source class;"ground"is the anchor class for mode A.BASE_OUTPUT_CLASS_NAMES (list of str) – Canonical output class names used by every spec.
"underhang"is the reclassification target.
- BASE_INPUT_CLASS_NAMES = ['ground', 'wall']
- BASE_OUTPUT_CLASS_NAMES = ['ground', 'wall', 'underhang']
- DEFAULT_NOISE_STD = 0.01
- DEFAULT_DEPTH_THRESHOLD = 0.2
- NOISY_MISLABEL_BUDGET = 0.01
- DEPTH_TOLERANCE = 0.05
- __init__()
Basic configuration for any VL3D test.
- Parameters:
name (str) – Test name
- run()
Run the taut string reclassifier test. Disables noisy logging (mirrors DR’s pattern), chains every sub-method through a boolean and-chain, and returns True only when all pass.
- Returns:
True when every assertion holds, False otherwise.
- Return type:
bool
- test_case_1_modeA()
Case 1, mode A: flat wall, no noise, no deviations. Expected: no reclassification.
- test_case_1_modeB()
Case 1, mode B: flat wall, no noise, no deviations. Expected: no reclassification.
- test_case_2_modeA()
Case 2, mode A: flat wall, noise, no deviations.
- test_case_2_modeB()
Case 2, mode B: flat wall, noise, no deviations.
- test_case_3_modeA()
Case 3, mode A: flat wall, no noise, one underhang.
- test_case_3_modeB()
Case 3, mode B: flat wall, no noise, one underhang.
- test_case_4_modeA()
Case 4, mode A: flat wall, noise, one underhang.
- test_case_4_modeB()
Case 4, mode B: flat wall, noise, one underhang.
- test_case_5_modeA()
Case 5, mode A: flat wall, no noise, three underhangs (small below D; mid and large above D).
- test_case_5_modeB()
Case 5, mode B: flat wall, no noise, three underhangs.
- test_case_6_modeA()
Case 6, mode A: flat wall, noise, three underhangs.
- test_case_6_modeB()
Case 6, mode B: flat wall, noise, three underhangs.
- test_case_7_modeA()
Case 7, mode A: quadric wall, no noise, no deviations.
- test_case_7_modeB()
Case 7, mode B: quadric wall, no noise, no deviations.
- test_case_8_modeA()
Case 8, mode A: quadric wall, noise, no deviations.
- test_case_8_modeB()
Case 8, mode B: quadric wall, noise, no deviations.
- test_case_9_modeA()
Case 9, mode A: quadric wall, no noise, one underhang.
- test_case_9_modeB()
Case 9, mode B: quadric wall, no noise, one underhang.
- test_case_10_modeA()
Case 10, mode A: quadric wall, noise, one underhang.
- test_case_10_modeB()
Case 10, mode B: quadric wall, noise, one underhang.
- test_case_11_modeA()
Case 11, mode A: quadric wall, no noise, three underhangs.
- test_case_11_modeB()
Case 11, mode B: quadric wall, no noise, three underhangs.
- test_case_12_modeA()
Case 12, mode A: quadric wall, noise, three underhangs.
- test_case_12_modeB()
Case 12, mode B: quadric wall, noise, three underhangs.
- test_case_13_modeA()
Case 13, mode A: flat wall, noise, one large overhang only. Mode A must NOT label the overhang. The signal column must carry
majorityfor the mode-A cluster.
- test_case_13_modeB()
Case 13, mode B: flat wall, noise, one large overhang only. Mode B must label the overhang as
underhang(mode B treats every above-threshold deviation asunderhang).
- test_case_14_modeA()
Case 14, mode A: flat wall, noise, one underhang + one overhang vertically separated. Mode A: only the underhang labelled. Signal:
majority.
- test_case_14_modeB()
Case 14, mode B: flat wall, noise, one underhang + one overhang. Both must be labelled.
- test_case_15_modeA()
Case 15, mode A: quadric wall, noise, two underhangs + two overhangs (one of each below threshold, one of each above). Mode A: only the above-threshold underhang labelled.
- test_case_15_modeB()
Case 15, mode B: quadric wall, noise, two underhangs + two overhangs. Both above-threshold deviations must be labelled.
- test_modeA_lower_ground_median()
Mode A terrace-on-cliff: ground on both sides of the wall but one side is significantly lower. The secondary
lower-ground-mediansignal must win the sign disambiguation.
- test_modeA_lower_ground_base()
Mode A free-standing pillar: ground on both sides at the SAME median elevation, but only one side extends down to the wall base. The tertiary
lower-ground-basesignal must win (i.e. the secondary lower-ground-median tie-break is forced into a tie by the matching medians; the algorithm then falls through to the base-restricted minimum comparison).
- test_modeA_inconclusive_fallback()
Mode A insufficient ground anchors: ground cluster too small within ground_search_radius. The signal must be
inconclusiveand the labels must match a pure mode-B run.
- test_skip_near_horizontal()
Near-horizontal cluster: a flat-roof patch mistakenly labelled as wall. The cluster must be skipped (signal
near-horizontal), its points must be unchanged, andn_skipped_near_horizontalmust be 1.
- test_thread_determinism_case14_modeA()
Thread determinism: run case 14 mode A with nthreads=1 and nthreads=8 and assert bit-identical output across every column plus byte-identical CSV reports.
- test_eager_validation_rejects_invalid_params()
Every entry of the seed’s Eager validation checklist must raise
ClassTransformerExceptionat __init__. Build a valid baseline spec, mutate one key per iteration, and assert __init__ throws.
- test_output_feature_inclusion_flags()
Per-flag output-feature inclusion: when each
include_*flag is True, the wrapper’slast_*attribute must be a non-None numpy array with the expected dtype after transform. When the flag is False, the wrapper sets the correspondinglast_*attribute to None (verified in the M6 wrapper code atsrc/utils/ctransf/taut_string_reclassifier.py:524).Wrapper-internal behaviour (M6): the C++ binding always receives
computeDepth/computeEigenmin/computeClusterIdxflags matching theinclude_*user flags. When a compute flag is False, the binding skips that column entirely and the wrapper stashesNoneon the correspondinglast_*. When True, the wrapper stashes a flattened numpy view of the binding’s output. Theinclude_*user flag therefore governs both (a) whether the column is computed, and (b) whether it is appended as a LAS extra dim bytransform_pcloud.
- test_empty_and_degenerate_scenarios()
Five degenerate sub-scenarios:
Zero wall points (all ground): empty-cloud no-op (yout == y, last_* None).
One wall point: min-cluster guard rejects, yout == y.
Wall shorter than sliding_window_size.
Wall narrower than bin_size.
Cluster rejected by minimum-cluster guard: cluster_idx has no gap on the survivors.
- test_info_logging_contract()
Capture LOG output during a small mode-A run and assert two transform-level INFO messages are emitted (start banner + end summary).