Skip to content

BUG: Fix wrong output and invalid input handling in 19 filters - #1765

Merged
imikejackson merged 18 commits into
BlueQuartzSoftware:developfrom
imikejackson:topic/batch2_defect_fixes
Oct 9, 2026
Merged

imikejackson merged 18 commits into
BlueQuartzSoftware:developfrom
imikejackson:topic/batch2_defect_fixes

Conversation

@imikejackson

@imikejackson imikejackson commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This change fixes defects that a code review found in filters that do not have a V&V report. Each defect makes a filter give wrong output, read or write past the end of an array, crash, or fail with a valid configuration. Each fix has a regression test that fails on develop. There is one commit for each filter or shared utility. No test data archive is added or changed. 70 files changed, +2612 / −318 lines.

Behavior changes

  • Copied child objects keep their names. When CropImageGeometry, ResampleImageGeom, PadImageGeometry, RotateSampleRefFrame, RemoveFlaggedVertices or CropEdgeGeometry write to a new geometry (not in place), a child object whose name contains the source geometry name is no longer renamed. For example, Data/Cell Feature Data copied to Cropped now gives Cropped/Cell Feature Data, not Cropped/Cell Feature Cropped. A saved pipeline that refers to the old, incorrect name must be updated.
  • New parameter component shapes. The Feature Ids parameter of ComputeFeatureBounds and the Mask Array and Cluster Ids parameters of Silhouette accept only 1-component DataArrays. Other shapes fail parameter validation (−208).
  • New preflight requirements. ComputeLargestCrossSections (−3711) and SurfaceNets (−56350 for Cell Feature Ids, −56351 for the selected cell DataArrays) require each cell-level input DataArray to have one tuple for each cell of the Image Geometry. The DataArray can be in any Attribute Matrix, and the Image Geometry does not need an assigned Cell Data Attribute Matrix.
  • WriteASCIIData Multiple Files mode rejects two selected DataArrays whose names are equal when case is ignored (−51003), because the macOS and Windows file systems ignore case.
  • ReadImage: the parameter "Put Input Origin at the Center of Geometry" is renamed "Center Geometry on (0, 0, 0)". The key and the behavior do not change.
  • RegularGridSampleSurfaceMesh: Use Custom Output Type is now directly above Output Type for Feature Ids and is linked to it. The keys do not change.

================================================================================

  1. Shared utilities
    ================================================================================

src/simplnx/DataStructure/DataPath.hpp/.cpp
src/simplnx/Utilities/ImageProcessing/ImageGeometryCrop.cpp
src/simplnx/Utilities/ImageProcessing/ImageGeometryResample.cpp

  • DataPath::rebase(oldPrefix, newPrefix) replaces the leading components of a path. It compares whole components, so the child Edge Geometry Ids does not start with Edge Geometry. DataPath::attemptRename now uses it. On develop, attemptRename is correct only when the old and new prefixes have the same depth. Pipeline rename tracking uses only prefixes of the same depth, so its behavior does not change.
  • Seven places that copy the children of a geometry or a DataGroup built the new path by replacing the source name everywhere in the path string. They now use DataPath::rebase. The places are CropEdgeGeometry, RotateSampleRefFrame, RemoveFlaggedVertices, PadImageGeometry, CopyDataObject, ImageGeometryCrop and ImageGeometryResample. On develop, a parent DataGroup whose name contains the geometry name is renamed too, so the copy fails with −5102.
  • A physical crop whose maximum equals the upper bound of the Image Geometry (origin + dimensions × spacing) is accepted. On develop, it fails with −5553, while a slightly larger value is clamped. This fixes ReadImage, CropImageGeometry, ReadZeissTxm, ReadNIfTI and ReadMha.
  • src/simplnx/Utilities/ImageIO/ImageStackCropping.hpp: the same fix for the Z axis of an image stack. A physical Z crop maximum equal to origin + number of files × spacing is accepted. On develop, it fails with −23525.

================================================================================
2. Image readers

src/Plugins/ImageProcessing/src/ImageProcessing/Filters/

  • ReadImage: a physical crop whose minimum is below the origin is clamped at execute, as preflight already does. On develop, execute fails with −2002. Preflight now returns the warnings of the crop helper, so the −50503 "clamped" warning reaches the user.
  • ReadImageStack: preflight and execute calculate the Z slice range of the full stack with one helper. On develop, a physical Z crop that does not start at the first slice fails at execute. A Z crop also moves the Z origin to the first kept slice, as X and Y crops do. Convert to Grayscale on an image with 1 or 2 components fails in preflight (−23507). On develop, it reads neighboring values or reads past the end of the buffer.

================================================================================
3. SimplnxCore filters

  • ComputeFeatureBounds: the edge geometry contains a box for every feature. On develop, the last box is lost. Each edge has the Feature Id of its feature, not the position of the box. The internal bounds buffer is sized by the Feature Data Attribute Matrix, so extra tuples are written as NaN instead of read past the end of the buffer.
  • ComputeLargestCrossSections: a Feature Ids DataArray whose tuple count is not the number of cells is rejected in preflight (−3711). On develop, a shorter DataArray is read past its end, and the values read are used as an index for a write. A correctly sized DataArray in another Attribute Matrix is still accepted.
  • Silhouette: the Direct algorithm maps sparse Cluster Ids such as {3, 7} to dense indices and rejects negative ids (−54081). On develop, both write past the end of a table. With Use Mask on, preflight checks that the mask, the Cluster Ids and the input DataArray have the same number of tuples (−8977). Points in cluster 0 use the mean distance to their own cluster, not the sum.
  • RegularGridSampleSurfaceMesh: a custom output type different from the Face Labels type works. On develop, it fails with bad_cast. A Face Labels value that does not fit in the output type is an execute error (−11801). A Face Label below 0 is treated as 0. On develop, the −1 exterior label that QuickSurfaceMesh and SurfaceNets write is copied into the output, so the rest of the row becomes −1.
  • QuickSurfaceMesh and SurfaceNets: the Direct algorithm checks that each selected feature DataArray has more tuples than the largest Feature Id (−62073), as the Scanline algorithm already does. On develop, Direct reads past the end of the DataArray and returns success.
  • SurfaceNets: Cell Feature Ids (−56350) and each selected cell DataArray (−56351) must have one tuple for each cell. On develop, a shorter DataArray is read past its end. With smoothing on, a negative Max Distance from Voxel Center (−56352) or Relaxation Iterations (−56353) fails in preflight. On develop, the negative distance is undefined behavior in std::clamp.
  • WriteASCIIData: on develop, two selected DataArrays with the same name in Multiple Files mode write the same file, and the second one overwrites the first.
  • CropEdgeGeometry: in Interpolate mode, a vertex outside the crop bounds that two crossing edges share is duplicated, one copy for each clip point. On develop, both edges use one clip point, so one edge moves off its line. StringArray and NeighborList data in the Vertex and Edge Attribute Matrices are copied with the kept vertices and edges. On develop, they make preflight throw bad_cast.

Test plan

  • Each new regression test passes. Each one failed on develop for the reason that its commit message gives.
  • The tests of filters with an in-core and an out-of-core algorithm run in each in-memory algorithm scenario.
  • The full test suite passes (3123 of 3124 locally on macOS; the one test not run needs Python, which the local build does not include, as on develop).
  • The example pipelines that use the changed filters preflight without new errors.
  • CI passes on all supported platforms.

@joeykleingers joeykleingers 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.

PR 1765 Code Review: BUG: Fix wrong output and invalid input handling in 19 filters

Reviewed commit 6b43dc5 (imikejackson, 13 commits, compared against merge base c008bcc on develop). Coverage: all 69 changed files. I read every source, header and doc diff in full and checked each fix against the develop code. For the 17 test files, I listed every added test case and read the CropEdgeGeometry and CopyDataObject tests in full. I did not read the other 15 test files line by line.

Findings

  • [Low] ReadImageStack still rejects a physical Z crop whose maximum is exactly the upper bound
    File: src/Plugins/ImageProcessing/src/ImageProcessing/Filters/Algorithms/ReadImageStackCropping.hpp:46 (ComputeCroppedZRange → ComputeCroppedZDimension)
    This PR makes a crop maximum of origin + dims × spacing valid for ReadImage, CropImageGeometry, ReadZeissTxm, ReadNIfTI and ReadMha, and ReadImageStack's X/Y crops get the fix through ReadImage. The Z axis doesn't. With 3 files, origin 0 and spacing 1, zBoundPhysical[1] = 3.0 passes the range check at ImageStackCropping.hpp:82. Then floor(3.0) = 3 >= zDim fails with -23525. So in one filter, X/Y accept the upper bound and Z rejects it. This bug predates the PR, but the PR fixes the same bug everywhere else. Fix: clamp zMaxIndex to zDim - 1 in ComputeCroppedZDimension, the way ImageGeometryCrop now does, and add a Crop_Physical_Z_MaxAtBound test. Any other caller of that shared helper gets the same fix.

  • [Low] The new location checks also reject Feature Ids that have the right size but sit in another Attribute Matrix
    Files: src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ComputeLargestCrossSectionsFilter.cpp:122, src/Plugins/SimplnxCore/src/SimplnxCore/Filters/SurfaceNetsFilter.cpp:161, :168
    The bug was reading past the end of a short DataArray. The fix rejects any DataArray whose parent isn't the geometry's Cell Data Attribute Matrix. That also rejects a Feature Ids array with exactly getNumberOfCells() tuples stored in another Attribute Matrix of the same shape, which worked on develop. The PR body states this behavior change, so it's a design question rather than a bug: comparing the tuple count with imageGeom.getNumberOfCells() would prevent the out-of-bounds read without breaking those layouts. Also, -3711 and -56350 are each used for two different conditions ("no Cell Data" and "wrong parent"). Separate codes would make the failure tests more specific.

  • [Low] No test covers the CopyDataObject fix when "Use New Parent" is on
    File: src/Plugins/SimplnxCore/src/SimplnxCore/Filters/CopyDataObjectFilter.cpp:132
    On develop, the created paths were made by replacing the target name in the path string. With Use New Parent on, that kept the old parent (Group/A_copy/child), but the object is copied to NewParent/A_copy/child. Using newDataPath in rebase fixes that case too, but the new test only checks the suffix case under the same parent. Add a section with k_UseNewParent_Key = true and a group child, and check that the declared paths are under the new parent.

  • [Nit] Anonymous namespace in a header
    File: src/Plugins/ImageProcessing/src/ImageProcessing/Filters/Algorithms/ReadImageStackCropping.hpp:6
    ZRange and ComputeCroppedZRange are defined in an unnamed namespace inside a header that two .cpp files include, so each file gets its own copy. This is the CERT DCL59-CPP pattern. Put them in nx::core (the function is already inline). The header also relies on other includes for std::floor and fmt::format. Include <cmath>, <fmt/format.h> and simplnx/Common/Result.hpp directly.

  • [Nit] Redundant comparison in the output-range check
    File: src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RegularGridSampleSurfaceMesh.cpp:419
    minLabel > outputMax || maxLabel > outputMax: minLabel <= maxLabel, so the first comparison never decides the result. Use maxLabel > outputMax.

C++ checks

  • Memory/lifetime: no issues. The CropEdgeGeometry parallel tasks borrow sourceIndices, and taskRunner.wait() returns before that vector is destroyed. NeighborList::setValue takes a copy of the vector, so duplicated rows don't share storage with the source.
  • Thread safety: no issues. In CopyAttributeArrays, StringArray and NeighborList data is copied on the calling thread while worker tasks write other arrays. In RegularGridSampleSurfaceMesh, the converted slice is written while the existing mutex is held.
  • Efficiency: no issues worth raising. RegularGridSampleSurfaceMesh allocates one converted buffer per slice, and only for a custom output type.
  • Formatting: clang-format 19.1.1 with the root .clang-format, dry run on all 50 changed C/C++ files: pass. CI's clang_format_pr check also passes.

Documentation

No issues found. The 19 doc changes match the code. For example, the -50503 clamp warning that ReadImage now passes through exists in ImageGeometryCrop.cpp:172-180, and the Silhouette text on the cluster-0 mean matches both algorithms.

Missing or failed evidence

  • I listed every added test case in 15 of the 17 test files and checked that it targets the stated defect, but I didn't read those test bodies line by line. CI passing confirms they run and pass. I didn't confirm locally that each one fails on develop; that comes from the PR description.
  • No local build or test run.

Confirmed correct

Each defect below is confirmed against the develop code:

  • DataPath::rebase / attemptRename: on develop, attemptRename overwrote only oldPath.getLength() components. A shorter newPath read past its end, and a longer one was cut short. rebase compares whole path components (Edge Geometry ≠ Edge Geometry Ids) and handles prefixes of any depth. Each call site's .value() can't fail, because child paths always start with the source path.
  • Child renaming by substring (7 places): StringUtilities::replace replaced every occurrence in the full path string, so Cell Feature Data became Cell Feature Cropped. All 7 call sites now use rebase.
  • Crop at the upper bound: floor((max − origin)/spacing) equals dims when max is exactly the upper bound, which tripped the xMax > numCells − 1 error (-5553). Clamping with std::min fixes it, and a value past the bound still goes to the existing clamp and warning branch.
  • ReadImage minimum below the origin: on develop, execute computed a negative voxel index and failed with -2002, while preflight clamped. Execute now clamps to 0 as well.
  • ReadImageStack Z range: on develop, execute converted physical Z through the destination geometry, which is already cropped and offset, so any crop not starting at slice 0 mapped to the wrong slice. Preflight and execute now use the same ComputeCroppedZRange. The Z origin shift is replaced by a postprocessed override, which runs as a deferred UpdateImageGeomAction (ReadImageStackFilter.cpp:395-400).
  • ComputeFeatureBounds: the currentOffset-- before resizing dropped the last box, and edge Feature Ids held the box index. The bounds buffer is now sized by the Feature Data Attribute Matrix, which the Split and Unified output loops already iterate over.
  • Silhouette Direct: the distance table had numClusters + 1 columns but was indexed by raw id, so sparse ids wrote past its end. The dense mapping matches the Scanline path. Empty clusters (ids found only in masked-out tuples) are left out of both the division and the minimum. Cluster 0 is now divided by its count, which matches the Scanline change.
  • RegularGridSampleSurfaceMesh: getDataRefAs<DataArray<T>> used the Face Labels type, so it threw bad_cast when the output type was different. The range check runs before workers are scheduled, and negative labels are clamped to 0 before the toggle logic.
  • QuickSurfaceMesh/SurfaceNets Direct: the selected feature DataArrays are now checked against the largest Feature Id, matching the Scanline path. The removed preflight variables were unused. A negative Max Distance gave std::clamp a low bound above its high bound, which is undefined behavior; preflight now rejects it.
  • WriteASCIIData: PrintDataSetsToMultipleFiles names each file <dir>/<targetName><ext> (OStreamUtilities.cpp:557), so two arrays with the same name wrote the same file.
  • CropEdgeGeometry: on develop, there was one clip point per outside vertex, so a second crossing reused the first clip point. The (vertex, clipPoint) key gives each crossing its own vertex, extra copies are added after the originals, and the else branch never reads srcVertices for a duplicate. On develop, StringArray and NeighborList preflight failed at dynamic_cast<const IDataArray&> with bad_cast.
  • CI: passes on macOS, both Ubuntu toolchains, Windows, and clang-format.

Verdict

APPROVE: every fix addresses a confirmed defect on develop and has a matching regression test. The findings are a related upper-bound case the PR doesn't cover, one design question, one untested branch, and two nits; none of them blocks merging.

imikejackson and others added 13 commits October 9, 2026 08:27
* attemptRename copied newPath.m_Path[i] for each index of oldPath. When
  the new prefix was shorter it read past its end, and when it was longer
  it dropped the extra components, so it was correct only for prefixes of
  the same depth. It now delegates to rebase()
* Add DataPath::rebase(oldPrefix, newPrefix), which returns the path with
  the leading oldPrefix replaced by newPrefix, comparing whole components
  (a child "Edge Geometry Ids" does not start with "Edge Geometry"). It
  returns std::nullopt when the path does not start with oldPrefix
* Add tests for rebase and for attemptRename with prefixes of different
  depth

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a filter copied the other children of a geometry (or of a copied
DataGroup) to a new location, it built each copied DataPath by replacing
every occurrence of the source name in the whole path string. A child
whose name contains the source name was renamed (Geom Feature Data under
Geom became Geom Out Feature Data), and a parent DataGroup whose name
contains it was renamed too, so the copy failed with -5102. Each site now
uses DataPath::rebase(), which replaces the leading path components only.

* CE-2: CropEdgeGeometry
* RotateSampleRefFrame
* RemoveFlaggedVertices
* PadImageGeometry
* CopyDataObject (the declared created paths of the copied children)
* ImageGeometryCrop (CropImageGeometry and the image readers that crop)
* ImageGeometryResample (ResampleImageGeom and ReadImageStack)
* Add a test for each filter and a sentence to each filter doc

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* RI-3: a physical crop whose maximum equals the upper bound of the Image
  Geometry (origin + dimensions x spacing) computed a maximum index equal
  to the number of cells, so preflight failed with -5553, while a slightly
  larger maximum was clamped with only a warning. The shared crop helper
  now clamps the computed maximum index to dimensions - 1
* The helper is shared, so this fixes ReadImage, CropImageGeometry,
  ReadZeissTxm, ReadNIfTI and ReadMha. Add a test for each caller and a
  doc sentence to each filter doc

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* RI-1: a physical crop whose minimum is below the image origin passed
  preflight (the shared crop helper clamps it to the first cell), then
  execute computed a negative start index and failed with -2002. Execute
  now uses the same minimum rule as preflight on X, Y and Z
* RI-1b: preflight discarded the warnings of the shared crop helper, so
  the -50503 "clamped" warning never reached the user. They are now
  returned with the preflight result
* RI-2: "Put Input Origin at the Center of Geometry" centers the Image
  Geometry on (0, 0, 0) and does not use the Origin value. The behavior
  does not change; the parameter is renamed "Center Geometry on (0, 0, 0)",
  its help text and the filter doc say what it does, and a test pins it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* RIS-1: execute looked up the physical Z crop in the already-cropped
  Image Geometry, so every physical Z crop that did not start at the first
  slice failed with -64512/-64513. Preflight and execute now compute the
  slice range of the full stack with one shared helper,
  ComputeCroppedZRange (ReadImageStackCropping.hpp)
* RIS-2: a Z crop did not shift the Z origin, so the volume was placed
  too low. The Z origin now moves to the first kept slice, as X/Y crops
  and ReadImage already do
* RIS-3: Convert to Grayscale on an image DataArray with 1 or 2
  components was not rejected (the parameter help says it is) and read
  neighboring values or past the end of the buffer. Preflight now returns
  -23507
* Document the Z origin shift and add tests for each defect

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* FB-1: with Create Bounding Box Geometries on, the counter of written
  boxes was decremented before the Edge Geometry was resized, so the last
  bounding box was always dropped (one feature gave an empty Edge
  Geometry). The decrement is removed in Direct and Scanline
* FB-2: the Edge Feature Ids DataArray held the box position 0, 1, 2...
  instead of the Feature Id of the feature each box bounds
* FB-3: the internal bounds buffer was sized by the largest Feature Id,
  but the output loops ran over the Feature Data Attribute Matrix tuple
  count, which the doc allows to be larger, so the extra tuples were read
  past the end of the buffer. The buffer is now sized by the Feature Data
  tuple count, and the extra tuples are written as NaN (this also fixes
  minor FB-4)
* CS-5: the Feature Ids parameter now declares a component shape of 1,
  because the algorithms read one Feature Id per element
* Document the edge geometry contents and NaN tuples; add tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* LCS-1: preflight did not check that the Feature Ids DataArray is a
  child of the Image Geometry's Cell Data Attribute Matrix. The parameter
  accepts a DataArray from any Attribute Matrix, so one with fewer tuples
  than the number of cells was read past its end and the values read were
  used as an index for a write. Preflight now returns -3711 when the Feature
  Ids parent is not the geometry's Cell Data. An Attribute Matrix only
  accepts children with its tuple count, so this also guarantees the
  {Z,Y,X} tuple shape that the Scanline path reads (minor LCS-3)
* The existing test fixtures now attach their Cell Data to the Image
  Geometry
* Document the requirement and add a test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* SIL-1: the Direct algorithm sized its distance table by the number of
  distinct Cluster Ids but indexed it by the raw id, so sparse ids such as
  {3, 7} or a negative id wrote past the end of the table. Direct now maps
  Cluster Ids to dense indices (0 stays 0), as Scanline does, and rejects
  negative ids with -54081
* SIL-2: with Use Mask on, preflight did not check that the Mask Array and
  Cluster Ids have the same number of tuples as the Attribute Array to
  Silhouette. Preflight now returns -8977
* SIL-3: for points in cluster 0, the mean distance to the point's own
  cluster was a sum, so their scores were wrong. Both algorithms now
  divide by the cluster size. Empty clusters cannot be the competing
  cluster
* CS-9, CS-10: the Mask Array and Cluster Ids parameters now declare a
  component shape of 1, because both algorithms read one value per tuple
* Document cluster 0 scoring and non-contiguous ids; add tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* RG-1: with Use Custom Output Type on, any Output Type for Feature Ids
  that differed from the Face Labels DataType made execute fail with
  bad_cast (-2). The write now dispatches on the output DataType and
  converts each slice when the types differ
* RG-1 (D-A6): a Face Labels value that does not fit in the chosen output
  type is an execute error, -11801, that names the value range and the
  type. Preflight cannot see the values
* Use Custom Output Type now sits directly above Output Type for Feature
  Ids and is linked to it, so the output type is shown only when the bool
  is on. Parameter keys do not change
* RG-2: Face Labels of -1, the exterior label that QuickSurfaceMesh and
  SurfaceNets write, were copied into the Feature Ids, so the rest of a
  row of cells became -1. A label below 0 is now treated as 0
* Document both rules and add tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* QSM-2: nothing checked that each selected feature DataArray has more
  tuples than the largest Feature Id. The Direct algorithm read past the
  end of a too-short feature DataArray and returned success, while
  Scanline returned -62073, so the two algorithms disagreed. Direct now
  makes the same check and returns -62073. The check depends on the
  Feature Id values, so it runs at execute
* Remove the misleading preflight comment that promised this check, and
  the unused variable next to it
* Document the requirement and add a test that runs both algorithms

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* WA-1: in Multiple Files mode, two selected DataArrays with the same name
  in different parents (for example /A/Data and /B/Data) were written to
  the same output file. The second file overwrote the first and the
  filter reported success. Preflight now returns -51003 when two selected
  DataArrays have the same name, ignoring case, because some file systems
  do not distinguish case
* Document the requirement and add a test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* SN-1: the Direct algorithm did not check that each selected feature
  DataArray has more tuples than the largest Feature Id, so a too-short
  DataArray was read past its end. Direct now makes the same check as
  Scanline and returns -62073
* SN-2: preflight did not check that Cell Feature Ids and the selected cell
  DataArrays are children of the Image Geometry's Cell Data Attribute
  Matrix, so a DataArray with fewer tuples than cells could be selected and
  read past its end. Preflight now returns -56350 (Cell Feature Ids, or no
  Cell Data Attribute Matrix) and -56351 (selected cell DataArrays)
* SN-4: with Apply smoothing operations on, a Max Distance from Voxel
  Center below 0 was undefined behavior (std::clamp with lo > hi) and a
  negative Relaxation Iterations was accepted silently. Preflight now
  returns -56352 and -56353
* Remove the unused preflight variable that suggested the SN-1 check
* Document the requirements and add tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t data

* CE-1: in Interpolate mode, when one vertex outside the crop bounds was
  shared by two edges that cross the bounds at different points, both
  edges got the same clipped vertex, so one edge was bent off its line.
  The outside vertex is now duplicated, one copy for each distinct clip
  point, and its vertex data is copied to each duplicate
* CE-3: a StringArray or NeighborList in the Vertex or Edge Attribute
  Matrix threw bad_cast out of preflight, which crashed the application.
  Preflight now creates these arrays, and execute copies them with the
  kept vertices and edges, using the same gather list as the DataArrays
* Document both behaviors and add tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@imikejackson
imikejackson force-pushed the topic/batch2_defect_fixes branch from 6b43dc5 to d17b0e9 Compare October 9, 2026 12:27
imikejackson and others added 5 commits October 9, 2026 12:06
* A physical Z crop maximum equal to origin + files x spacing passed the
  range check (-23523), but floor() then gave a slice index equal to the
  file count, which failed with -23525. The X and Y axes and the other
  image readers already accept this value. ComputeCroppedZDimension now
  clamps the maximum slice index to the last slice, the same way
  ImageGeometryCrop does, and -23525 is no longer reachable
* Add Crop_Physical_Z_MaxAtBound test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ComputeLargestCrossSections and SurfaceNets rejected any Feature Ids
  (or SurfaceNets selected cell DataArray) that was not in the Image
  Geometry's Cell Data Attribute Matrix. That also rejected correctly
  sized DataArrays in another Attribute Matrix, which worked on develop
* Preflight now checks that the tuple count equals the number of cells,
  which still prevents the read past the end of a short DataArray. The
  Image Geometry no longer needs an assigned Cell Data Attribute Matrix
* Each error code now has one meaning: -3711 (Feature Ids tuple count),
  -56350 (Cell Feature Ids tuple count), -56351 (selected cell DataArray
  tuple count). The error messages give both counts
* Add tests for too-short DataArrays and for correctly sized DataArrays
  in another Attribute Matrix; update the docs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Copy a DataGroup with a child to a new parent and check that the
  declared and copied paths are under the new parent, not the old one

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ZRange and ComputeCroppedZRange are in a header, so put them in
  nx::core instead of an unnamed namespace (CERT DCL59-CPP)
* Include <cmath>, <fmt/format.h> and Result.hpp directly

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h range check

* minLabel <= maxLabel, so only maxLabel > outputMax decides the result

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@imikejackson
imikejackson merged commit 1986127 into BlueQuartzSoftware:develop Oct 9, 2026
5 checks passed
@imikejackson
imikejackson deleted the topic/batch2_defect_fixes branch October 12, 2026 19:53
imikejackson added a commit to imikejackson/simplnx that referenced this pull request Oct 12, 2026
* Add IsChildOfAttributeMatrix(dataStructure, arrayPath, attributeMatrix)
  to DataArrayUtilities. It compares DataObject ids, so it is correct when
  the Attribute Matrix can be reached through more than one parent path,
  and it returns false when the path has no parent
* ComputeLargestCrossSections (-3711) and SurfaceNets (-56350, -56351)
  keep the tuple-count checks merged in BlueQuartzSoftware#1765, which accept correctly
  sized DataArrays from another Attribute Matrix, so they do not use the
  helper. RegularGridSampleSurfaceMesh uses it in a later commit
* Add unit tests for the helper

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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