Skip to content

VV: Multi-Threshold Objects - #1688

Open
mmarineBlueQuartz wants to merge 14 commits into
BlueQuartzSoftware:developfrom
mmarineBlueQuartz:vv/MultiThresholdObjects
Open

VV: Multi-Threshold Objects#1688
mmarineBlueQuartz wants to merge 14 commits into
BlueQuartzSoftware:developfrom
mmarineBlueQuartz:vv/MultiThresholdObjects

Conversation

@mmarineBlueQuartz

Copy link
Copy Markdown
Collaborator
  • Add tests for untested code coverage.
  • Add V&V documentation.
  • Fix bug regarding thresholding over ThresholdSets.
  • Fix bug where inverting mask values does not work.

Naming Conventions

Naming of variables should descriptive where needed. Loop Control Variables can use i if warranted. Most of these conventions are enforced through the clang-tidy and clang-format configuration files. See the file simplnx/docs/Code_Style_Guide.md for a more in depth explanation.

Filter Checklist

The help file simplnx/docs/Porting_Filters.md has documentation to help you port or write new filters. At the top is a nice checklist of items that should be noted when porting a filter.

Unit Testing

The idea of unit testing is to test the filter for proper execution and error handling. How many variations on a unit test each filter needs is entirely dependent on what the filter is doing. Generally, the variations can fall into a few categories:

  • 1 Unit test to test output from the filter against known exemplar set of data
  • 1 Unit test to test invalid input code paths that are specific to a filter. Don't test that a DataPath does not exist since that test is already performed as part of the SelectDataArrayAction.

Code Cleanup

  • No commented out code (rare exceptions to this is allowed..)
  • No API changes were made (or the changes have been approved)
  • No major design changes were made (or the changes have been approved)
  • Added test (or behavior not changed)
  • Updated API documentation (or API not changed)
  • Added license to new files (if any)
  • Added example pipelines that use the filter
  • Classes and methods are properly documented

* Updated MultiThresholdObjects algorithm to account for the IsInverted state allowed by individual thresholds.
* WIP: Replacing unit tests with smaller datasets and standardized testing functions. Integer and floating point single component DataArrays are tested for all comparison types and inversion states using a single threshold. Multicomponent arrays are in the process of being tested and the filter was updated for assumptions that may be wrong. The documentation and GUI need to be referenced before moving forward. Multicomponent threshold tests and threshold creation will likely need to be adjusted based on new information.
* TODO: Create tests for entire threshold sets and even nested sets.
* Mask array is always 1 component.
* Update unit tests for multicomponent array thresholds
* Consolidated unit tests of the same array type and component count using GENERATE.
* Added additional value checks.
* All single threshold tests pass.
* Removed requirement for input arrays to all have the same number of components, Each threshold specifies the target component.
* MultiThresholdObjects no longer writes directly to the DataStore when running Thresholds. Instead Sets and Thresholds both store temporary vectors that are copied to the parent set's vector. The topmost ThresholdSet copies the vector to the DataStore upon completion.
* Added ThresholdSet unit tests.
* Standardized apply threshold values between thresholds and sets.
* Removed unnecessary inversion parameter in threshold and set algorithm
* Re-enabled unit test without the Mismatched components section. That case is no longer an error.
* Added function documentation for ApplyThresholdValues
* Simplified InsertThreshold parameters.
* Deleted unused ThresholdValueFunctor struct.
* Re-enabled invalid execution and mask DataType unit tests and updated for new tuple counts.
* Simplified mask DataType unit tests to remove duplicated code.
* Removed unused legacy unit tests.
* Converted std::vector data to AbstractDataStore<T> using DataStoreUtilities.
Removed stale testing constant.
@imikejackson imikejackson changed the title VV/Multi Threshold Objects VV: Multi-Threshold Objects Jul 24, 2026
@imikejackson

Copy link
Copy Markdown
Contributor

Full V&V review of this PR: every claim in the V&V documents was re-verified against head 909bff118, plus an adversarial algorithm review, a CPU/memory review, and an independent legacy 6.5.171 A/B run (which this PR's own deviations doc lists as outstanding).

Independent A/B validation (run for this review)

Shared legacy-format input (100 tuples, Int32 = 0..99, Float32 = 0.01*(i+1)); PipelineRunner 6.5.171 vs this branch's nxrunner vs an independent numpy oracle:

Case Config Legacy filter Result
AB1 Int32 > 42 AND Float32 < 0.70 (flat) MultiThresholdObjects MATCH (legacy = NX = oracle)
AB2 Int32 > 20 AND (Float32 < 0.60 OR Int32 == 55) (nested set) MultiThresholdObjects2 MATCH
AB3 Int32 < 80 OR NOT(Int32 > 30 AND Float32 < 0.95) (inverted nested set) MultiThresholdObjects2 MATCH

Same three configs at 50M random tuples: this branch matches the oracle exactly. All 17 ctest entries pass locally.

The same pipelines run against develop (pre-PR): AB2 yields an all-false mask (38/100 wrong) and AB3 differs in 51/100 values. Both bug-fix claims in the PR description are real and the fix restores legacy semantics — legacy's invertThreshold() flips values element-wise; the old NX std::reverse was a misport, and the old per-item functor dispatch broke nested-set combination entirely.

Also confirmed from legacy source: MultiThresholdObjects2::dataCheck() rejects non-scalar arrays (error -11003), so component-index selection is NX-only.

Actionable items

Correctness / behavior

  • Restore the cancel check. operator()() no longer checks m_ShouldCancel anywhere (the member is now unused). This reverts the cancel check deliberately added by ENH: Add missing cancel checks to lots of filters #1582 and makes large thresholding runs uncancellable. A per-threshold check inside ThresholdSet's loop (or per-chunk) would restore it.
  • Restore the SIMPL backwards-compatibility test. TEST_CASE "SIMPL Backwards Compatibility" was deleted, reverting coverage re-enabled by BUG: Fix SIMPL JSON conversion segfault and re-enable backwards-compatibility checks #1605. The 6.4/6.5 fixtures still exist under test/simpl_conversion/ but nothing exercises them now — and the V&V report cites them as evidence.
  • Restore valid-execution coverage for custom TRUE/FALSE values. TEMPLATE_TEST_CASE "Valid Execution - Custom Values" (10 types) was deleted; only the out-of-bounds/boolean error paths remain. The rewritten InsertThreshold combines via == trueValue / == falseValue, so custom values interact directly with the new logic and currently have zero execution coverage.
  • Decide the fate of ErrorCodes::UnequalComponents (-4001) (MultiThresholdObjectsFilter.hpp:40) — dead after the preflight component-count check removal. Remove it or mark it retired (removing changes the public enum; either way the report should note the relaxed preflight behavior).

V&V document corrections (each verified against head)

  • Bug flags: None identified is wrong — this PR itself fixes two user-facing bugs, confirmed by the A/B above (develop produces an all-false mask for a plain nested set). Document both bugs in the report, with a note for users of earlier NX releases whose nested-set masks were silently wrong.
  • Legacy comparison is no longer outstanding — fold in the A/B results above (flat vs MultiThresholdObjects, nested + inverted vs MultiThresholdObjects2, all match at head). The deviations file can move past "comparison not yet run"; expected entry count stays 0 for these configs.
  • Resolve the open question on component-index: legacy Advanced rejects non-scalar arrays (-11003), so it had no equivalent — the feature is NX-only.
  • Status fields are inconsistent: header says READY FOR REVIEW, Sign-off says pending — DRAFT, At-a-glance lists "promotion DRAFT → READY FOR REVIEW" as outstanding.
  • Test inventory lists every test as kept/new but omits the two removed TEST_CASEs (Valid Execution - Custom Values, SIMPL Backwards Compatibility). Removals must appear as retired with a reason.
  • Stale note: k_MismatchingComponentsArrayPath "leftover at line 31 worth deleting" — it no longer exists on this branch.
  • "Mask DataType — 11 SECTIONs, one per mask-output DataType": the TEST_CASE has 10 SECTIONs (boolean is not a SECTION; it's covered via defaults elsewhere, as the same sentence says).
  • Material-PRs-since-baseline should include ENH: Add missing cancel checks to lots of filters #1582 (cancel checks — reverted here) and BUG: Fix SIMPL JSON conversion segfault and re-enable backwards-compatibility checks #1605 (SIMPL conversion test — deleted here).
  • Code-path table should enumerate the custom TRUE/FALSE execution path (currently uncovered per above), so the "23 of 24" figure is accurate after the fix.

CPU / memory

Measured on 50M tuples (3 threshold filters per pipeline, Release): develop 18.2s / 774MB peak RSS → this PR 21.9s / 861MB. Filter-only time roughly +50%; peak temp usage is now (nesting depth + 2) full-size stores vs 1 std::vector before.

  • Drop the functor-level temp + copy loop (ThresholdSetFunctor, Algorithms/MultiThresholdObjects.cpp:197-216). ThresholdSet already produces the complete result; pre-fill the output store with falseValue and pass it directly — saves one N-size allocation and two full passes.
  • The std::fill in ThresholdValue (line 147) is redundant — ThresholdFilterHelper writes every element.
  • Hoist the if(inverse) per-element branch out of InsertThreshold's loop.
  • Temps moved from std::vector to AbstractDataStore — every hot-loop access is now a virtual call, which is where the measured slowdown comes from. If OOC-capable temps are the goal, consider bulk/chunked access; per-element operator[] on a chunked HDF5-backed temp will thrash. Also worth confirming temp stores created via DataStoreUtilities::CreateDataStore under force-large-data prefs are cleaned up.

Test quality / cleanup

  • Dead debug locals in CheckThresholdSet2 (test cpp 607-608): value and expected computed then ignored.
  • maskArray dereferenced without a null check (test cpp 1133-1134); project convention is REQUIRE_NOTHROW + getDataRefAs.
  • 6 of 9 TEST_CASEs are missing UnitTest::CheckArraysInheritTupleDims(dataStructure) (project testing convention) — all the new Valid Single Thresholds / Threshold Sets / Input Array DataType cases.
  • operator()() leftovers: firstValueFound only feeds a constant !false, and the thresholdSet local (line 249) is unused.
  • Doxygen on InsertThreshold / ApplyThresholdValues is stale (parameter names no longer exist; "threshould" typo).

Verified good

  • UUID mapping (both legacy UUIDs → this filter), FromSIMPLJson dual-converter branching, filter UUID, and the recursive component-index preflight check all match the report.
  • 9 TEST_CASE groups / 17 ctest entries, all passing locally; CI green on all platforms.
  • Class 1 analytical oracle with in-memory fixtures and no exemplar archive — complies with the no-circular-oracle policy; the test matrix genuinely enumerates operator x invert x union x nesting x both DataType axes.
  • The algorithm restructure itself is semantically correct against both legacy filters (A/B above), including the forced-OR seeding of each set's accumulator.

@imikejackson imikejackson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants