src.tests.taut_string_reclassifier_test

Classes

TautStringReclassifierTest()

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 majority for 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 as underhang).

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-median signal 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-base signal 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 inconclusive and 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, and n_skipped_near_horizontal must 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 ClassTransformerException at __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’s last_* attribute must be a non-None numpy array with the expected dtype after transform. When the flag is False, the wrapper sets the corresponding last_* attribute to None (verified in the M6 wrapper code at src/utils/ctransf/taut_string_reclassifier.py:524).

Wrapper-internal behaviour (M6): the C++ binding always receives computeDepth / computeEigenmin / computeClusterIdx flags matching the include_* user flags. When a compute flag is False, the binding skips that column entirely and the wrapper stashes None on the corresponding last_*. When True, the wrapper stashes a flattened numpy view of the binding’s output. The include_* user flag therefore governs both (a) whether the column is computed, and (b) whether it is appended as a LAS extra dim by transform_pcloud.

test_empty_and_degenerate_scenarios()

Five degenerate sub-scenarios:

  1. Zero wall points (all ground): empty-cloud no-op (yout == y, last_* None).

  2. One wall point: min-cluster guard rejects, yout == y.

  3. Wall shorter than sliding_window_size.

  4. Wall narrower than bin_size.

  5. 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).