Repository navigation
Conversation
Adds cuNCorr (submodule Tools/cuNCorr) as a selectable DIC backend. cuNCorr runs
on CUDA when a device is present, else its CPU backend — numerically identical
(shared cores; cuNCorr's cuda_parity_test gates it).
- Tools/cuNCorr submodule; CMake builds the standalone `cuncorr` lib
(tests/apps/its-own-CppNCorr disabled; cppxdic keeps its ncorr target).
Configure -DCUNCORR_BACKEND=CUDA on a GPU node.
- Adapter src/dic/cuncorr_dic.{h,cpp}: runs cuNCorr sequence mode (fixed
reference, warm-started frames) and packages results as ncorr::DIC_analysis_output
(raw Lagrangian/pixel, reduced grid). Grids align exactly with ncorr
((dim-1)/sf+1 == ceil(dim/sf); reduced (r,c) <-> full (r*sf,c*sf)), so the
.bin + StepE reconstruction consume it unchanged.
- Dispatch at both DIC call sites (single_dic_workflow.cpp, step_d_workflow.cpp)
on Config::dic_engine ("cuncorr" default | "ncorr" legacy) + cuncorr_seed_search.
- tests/cuncorr_adapter_test.cpp (opt-in -DBUILD_CUNCORR_ADAPTER_TEST=ON):
synthetic translation recovery through the adapter.
Verified: cppxdic + singledic + adapter test all build & link against cuncorr+ncorr
(CPU). Runtime of OpenCV-linked binaries blocked locally by a broken brew
protobuf dylib (env, not code); runs on the cluster.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI failures on PR #38: - build/test/coverage: add_subdirectory(Tools/cuNCorr) failed because the CI runner does not check out the private cuNCorr submodule. cuNCorr is now OPTIONAL: guarded on Tools/cuNCorr/CMakeLists.txt existing (+ new option CPPXDIC_USE_CUNCORR, default ON). Absent -> ncorr-only build; the adapter source, its link, and the dispatch branches are gated behind CPPXDIC_HAVE_CUNCORR in single_dic_workflow.cpp and step_d_workflow.cpp. - lint: clang-format (18.1.8) the three added files. Also switch the cuNCorr submodule URL to https (consistent with CppNCorr; works with a token that has org access). Verified locally: builds with cuNCorr ENABLED (adapter compiled) and with -DCPPXDIC_USE_CUNCORR=OFF (ncorr-only, what CI does). clang-format --dry-run --Werror clean on the added files. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… public) cuNCorr is a public repo, so CI can fetch the submodule. - build job: checks out Tools/cuNCorr, compiles cppxdic WITH the cuNCorr engine (CPPXDIC_HAVE_CUNCORR), and runs the adapter synthetic-recovery test (-DBUILD_CUNCORR_ADAPTER_TEST=ON) — validates run_cuncorr_dic end to end on CPU. - test + coverage jobs: pinned ncorr-only (-DCPPXDIC_USE_CUNCORR=OFF) so the ohtcfrp golden regressions keep validating the CppNCorr reference engine (cuNCorr is a different algorithm with different numbers). Verified locally: the build-job commands build cppxdic + cuncorr_adapter_test with the engine ENABLED. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- deploy/cluster/cppxdic_gpu.def: single Apptainer image on nvidia/cuda:12.4.1 building cuNCorr with -DCUNCORR_BACKEND=CUDA for sm 70/75/80/86/89 (all Manneback GPU types) + hdf5-tools (h5diff) and gdb + a RelWithDebInfo cppxdic_dbg for cluster debugging. One image serves GPU and CPU runs (cuNCorr falls back to its CPU backend without --nv), keeping binaries identical for bit-comparisons. - CMakeLists: turn CUDA_SEPARABLE_COMPILATION OFF on the cuncorr target — the non-CUDA executables (cppxdic, singledic) cannot generate the device-link step an rdc static lib requires (and CMake < 3.23 cannot resolve device symbols on static libraries); cuNCorr's kernels are TU-local so rdc was never needed. Without this the link fails with undefined __cudaRegisterLinkedBinary_* symbols. - generate_ncorr_bin: call H5Rdereference2 explicitly behind a version guard — Ubuntu 22.04's hdf5 1.10 maps the H5Rdereference macro to the 3-arg v1.8 signature and the build fails inside the container. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two fixes surfaced by the first end-to-end cuNCorr runs:
1. formatOutput used a "value == 0.0 -> invalid" sentinel when building
myDIC2DpairResults Points/CorCoeffVec. ncorr zero-fills invalid points
so it mostly worked, but a VALID point can hold an exact 0.0 — notably
cuNCorr's whole first frame (self-match returns u=v=0 exactly), which
turned Points{1} (the reference seed grid, stored by MATLAB and ncorr
alike) into all-NaN. Validity now comes from each frame's disp ROI mask
(the set of converged points) in fillDirectFrame, the cam2 interpolation
sampler, and the matching check. Behavior-preserving for ncorr.
2. Inter-camera MATCHING calls were dispatched to cuNCorr like tracking.
Camera disparities (~276 px here) far exceed cuNCorr's local coarse
seed search (cuncorr_seed_search, 15 px) — only ncorr's GLOBAL seed
optimization bridges them — so matching silently produced an all-zero
field with an empty valid ROI, cascading into all-NaN cam-def Points,
all-NaN 3D vertices, and a 0-face stitched mesh. Matching calls
(<= 2 current images, not perf-critical) now stay on ncorr; cuNCorr
keeps the frame-series tracking it was designed for.
Verified end-to-end on S09 trial 7: GPU run completes Steps D+E+F in
32 min with healthy cam-def Points (4% NaN, ROI edges only) and the same
stitched mesh (6726 points / 12071 faces) as the ncorr CPU run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
deploy/cluster/cpugpu/: SLURM harness for the ncorr-CPU vs cuNCorr-GPU study on S09 trials 7/12/25 — isolated per-run dic_paths, explicit dic_engine (the compiled-in default is cuncorr!), engine/backend verification, axes for threads/GPU/exact_matlab/updates/fixed-step/stages, crash-safe meta on home (globalscratch is purgeable). Plus: - compare_bits.sh: bit-level comparison (h5diff on .mat — never md5, the v7.3 userblock embeds timestamps — and cmp on .bin). - compare_dic.cpp: matio-based semantic comparator for MATLAB (v5 + struct-array layout) vs cppxdic (v7.3 + cells) files — recursive walk, NaN-aware, point-matched trajectory mode, DUMP/LIST probes with deep loading of deferred HDF5 data. - results/REPORT.md + summaries: GPU ~4x faster than 32-core CPU at ~15x less memory; GPU bit-deterministic across reps; cuncorr CPU-vs-GPU differences bounded by 1e-12 (all trials); MATLAB agreement sub-0.1 px for the bulk with the boundary band explained by MATLAB's FFT-bcoef border bias (fixed on our side by CppNCorr 4758ad2); full-pipeline Step-E/F bug chain documented. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
johnaoga
added a commit
that referenced
this pull request
Sep 3, 2026
* feat(dic): integrate cuNCorr as GPU/CPU DIC engine (cppxdic -> cuncorr)
Adds cuNCorr (submodule Tools/cuNCorr) as a selectable DIC backend. cuNCorr runs
on CUDA when a device is present, else its CPU backend — numerically identical
(shared cores; cuNCorr's cuda_parity_test gates it).
- Tools/cuNCorr submodule; CMake builds the standalone `cuncorr` lib
(tests/apps/its-own-CppNCorr disabled; cppxdic keeps its ncorr target).
Configure -DCUNCORR_BACKEND=CUDA on a GPU node.
- Adapter src/dic/cuncorr_dic.{h,cpp}: runs cuNCorr sequence mode (fixed
reference, warm-started frames) and packages results as ncorr::DIC_analysis_output
(raw Lagrangian/pixel, reduced grid). Grids align exactly with ncorr
((dim-1)/sf+1 == ceil(dim/sf); reduced (r,c) <-> full (r*sf,c*sf)), so the
.bin + StepE reconstruction consume it unchanged.
- Dispatch at both DIC call sites (single_dic_workflow.cpp, step_d_workflow.cpp)
on Config::dic_engine ("cuncorr" default | "ncorr" legacy) + cuncorr_seed_search.
- tests/cuncorr_adapter_test.cpp (opt-in -DBUILD_CUNCORR_ADAPTER_TEST=ON):
synthetic translation recovery through the adapter.
Verified: cppxdic + singledic + adapter test all build & link against cuncorr+ncorr
(CPU). Runtime of OpenCV-linked binaries blocked locally by a broken brew
protobuf dylib (env, not code); runs on the cluster.
* fix(ci): make cuNCorr optional + clang-format the adapter files
CI failures on PR #38:
- build/test/coverage: add_subdirectory(Tools/cuNCorr) failed because the CI
runner does not check out the private cuNCorr submodule. cuNCorr is now
OPTIONAL: guarded on Tools/cuNCorr/CMakeLists.txt existing (+ new option
CPPXDIC_USE_CUNCORR, default ON). Absent -> ncorr-only build; the adapter
source, its link, and the dispatch branches are gated behind CPPXDIC_HAVE_CUNCORR
in single_dic_workflow.cpp and step_d_workflow.cpp.
- lint: clang-format (18.1.8) the three added files.
Also switch the cuNCorr submodule URL to https (consistent with CppNCorr; works
with a token that has org access).
Verified locally: builds with cuNCorr ENABLED (adapter compiled) and with
-DCPPXDIC_USE_CUNCORR=OFF (ncorr-only, what CI does). clang-format --dry-run
--Werror clean on the added files.
* ci: build + adapter-test the cuNCorr integration (now that cuNCorr is public)
cuNCorr is a public repo, so CI can fetch the submodule.
- build job: checks out Tools/cuNCorr, compiles cppxdic WITH the cuNCorr engine
(CPPXDIC_HAVE_CUNCORR), and runs the adapter synthetic-recovery test
(-DBUILD_CUNCORR_ADAPTER_TEST=ON) — validates run_cuncorr_dic end to end on CPU.
- test + coverage jobs: pinned ncorr-only (-DCPPXDIC_USE_CUNCORR=OFF) so the
ohtcfrp golden regressions keep validating the CppNCorr reference engine
(cuNCorr is a different algorithm with different numbers).
Verified locally: the build-job commands build cppxdic + cuncorr_adapter_test
with the engine ENABLED.
* build(gpu): make the CUDA image buildable (cppxdic_gpu.def)
- deploy/cluster/cppxdic_gpu.def: single Apptainer image on
nvidia/cuda:12.4.1 building cuNCorr with -DCUNCORR_BACKEND=CUDA for
sm 70/75/80/86/89 (all Manneback GPU types) + hdf5-tools (h5diff) and
gdb + a RelWithDebInfo cppxdic_dbg for cluster debugging. One image
serves GPU and CPU runs (cuNCorr falls back to its CPU backend without
--nv), keeping binaries identical for bit-comparisons.
- CMakeLists: turn CUDA_SEPARABLE_COMPILATION OFF on the cuncorr target —
the non-CUDA executables (cppxdic, singledic) cannot generate the
device-link step an rdc static lib requires (and CMake < 3.23 cannot
resolve device symbols on static libraries); cuNCorr's kernels are
TU-local so rdc was never needed. Without this the link fails with
undefined __cudaRegisterLinkedBinary_* symbols.
- generate_ncorr_bin: call H5Rdereference2 explicitly behind a version
guard — Ubuntu 22.04's hdf5 1.10 maps the H5Rdereference macro to the
3-arg v1.8 signature and the build fails inside the container.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dic): cuNCorr pair-results validity + keep camera matching on ncorr
Two fixes surfaced by the first end-to-end cuNCorr runs:
1. formatOutput used a "value == 0.0 -> invalid" sentinel when building
myDIC2DpairResults Points/CorCoeffVec. ncorr zero-fills invalid points
so it mostly worked, but a VALID point can hold an exact 0.0 — notably
cuNCorr's whole first frame (self-match returns u=v=0 exactly), which
turned Points{1} (the reference seed grid, stored by MATLAB and ncorr
alike) into all-NaN. Validity now comes from each frame's disp ROI mask
(the set of converged points) in fillDirectFrame, the cam2 interpolation
sampler, and the matching check. Behavior-preserving for ncorr.
2. Inter-camera MATCHING calls were dispatched to cuNCorr like tracking.
Camera disparities (~276 px here) far exceed cuNCorr's local coarse
seed search (cuncorr_seed_search, 15 px) — only ncorr's GLOBAL seed
optimization bridges them — so matching silently produced an all-zero
field with an empty valid ROI, cascading into all-NaN cam-def Points,
all-NaN 3D vertices, and a 0-face stitched mesh. Matching calls
(<= 2 current images, not perf-critical) now stay on ncorr; cuNCorr
keeps the frame-series tracking it was designed for.
Verified end-to-end on S09 trial 7: GPU run completes Steps D+E+F in
32 min with healthy cam-def Points (4% NaN, ROI edges only) and the same
stitched mesh (6726 points / 12071 faces) as the ncorr CPU run.
* feat(study): CPU-vs-GPU comparison harness + results (Manneback)
deploy/cluster/cpugpu/: SLURM harness for the ncorr-CPU vs cuNCorr-GPU
study on S09 trials 7/12/25 — isolated per-run dic_paths, explicit
dic_engine (the compiled-in default is cuncorr!), engine/backend
verification, axes for threads/GPU/exact_matlab/updates/fixed-step/stages,
crash-safe meta on home (globalscratch is purgeable). Plus:
- compare_bits.sh: bit-level comparison (h5diff on .mat — never md5, the
v7.3 userblock embeds timestamps — and cmp on .bin).
- compare_dic.cpp: matio-based semantic comparator for MATLAB (v5 +
struct-array layout) vs cppxdic (v7.3 + cells) files — recursive walk,
NaN-aware, point-matched trajectory mode, DUMP/LIST probes with deep
loading of deferred HDF5 data.
- results/REPORT.md + summaries: GPU ~4x faster than 32-core CPU at ~15x
less memory; GPU bit-deterministic across reps; cuncorr CPU-vs-GPU
differences bounded by 1e-12 (all trials); MATLAB agreement sub-0.1 px
for the bulk with the boundary band explained by MATLAB's FFT-bcoef
border bias (fixed on our side by CppNCorr 4758ad2); full-pipeline
Step-E/F bug chain documented.
* feat(dic): fixed-step reference updates + no_update tracking-preset option
Wire two ncorr_params.txt options into Step-D tracking (both default to the
long-standing behavior):
- fixed_step_ref = N (>0): force a reference change every N frames — MATLAB
ncorr step analysis semantics (stepanalysis.step / step_ref_change). Bumps
Tools/CppNCorr to feat/fixed-step-ref, which caps seed segments at N frames
in matlab_compute_seed_segment; the existing segment loop does the seed
propagation and the exact_matlab_* path chains segments back to the global
reference (ncorr_alg_addanalysis port). Verified on S09 t7 (150 frames,
step 10): 60 segments = 150/10 x 4 tracking calls, matching MATLAB's
recorded dispinfo.stepanalysis exactly.
- no_update = false: KEEP_MOST_POINTS preset (with cutoff_corrcoef=0.5 ->
correlation-triggered reference updates); default true keeps NO_UPDATE.
The tracking call site previously hardcoded true.
Study finding (deploy/cluster/cpugpu/results/REPORT.md): reproducing MATLAB's
fixed-step structure is numerically WORSE at the 0.1 px level than direct
fixed-reference tracking — per-boundary composition differences compound over
15 boundaries — so the production default stays fixed-reference; fixed_step_ref
remains available for data where a fixed reference genuinely decorrelates.
* fix(step-ef): resolve the Step-E stitching-save segfault chain
The long-standing "segfault at Step E/F" was three stacked bugs, each
hiding the next (gdb-diagnosed on Manneback, full chain in
deploy/cluster/cpugpu/results/REPORT.md):
1. mat_writer embedded AllPairsResults/DIC2Dinfo/ROImask into the
DIC3Dcombined struct by WRITING them as top-level variables into the
output file and Mat_VarRead-ing them back through the write handle —
the returned matvars carry live HDF5-internal state and crash
Mat_VarSetStructFieldByName. Replaced with pure in-memory builders
(buildAllPairsResultsVar / buildDIC2DinfoVar; ROImask built directly
from the cv::Mat). Also removes the duplicated top-level variables
(~half the output size).
2. A crash mid-save left a truncated DIC3Dcombined that later runs
accepted as a valid checkpoint (existence-only test) and then failed
on. dic_analysis now writes to a .partial file and renames on success.
3. With E finally producing valid files, Step F aborted with "0 faces":
v7.3 reads defer nested cell/field data and mat_reader's per-field
ensure_loaded patchwork missed Faces (and five other fields). Replaced
with one recursive deep_load of the whole struct tree.
Verified end-to-end (S09 t7, both engines): Steps D+E+F complete rc=0,
identical stitched meshes (6726 points / 12071 faces), 1.7 GB
DIC3DPPresults from Step F. GPU 32 min, ncorr-CPU 75 min.
* feat(dic): wire the dangling perspective_interp tag (biquintic Eulerian conversion)
The `perspective_interp` ncorr-params key has been parsed since March
(e53dc45) but consumed by nothing. It now selects the interpolation for the
Eulerian perspective change: false (default) keeps the bicubic CUBIC_KEYS
workaround — a relic of the FFT-bcoef border bias era, preserved so existing
outputs are unchanged — and true selects biquintic B-spline, matching MATLAB
ncorr, which is numerically safe again since CppNCorr replaced the FFT
circular deconvolution with the Unser recursive filter (CppNCorr 4758ad2).
Background: the "48 px / 20 px border padding" investigation
(deploy/cluster/cpugpu/results/REPORT.md) — MATLAB still carries the
FFT-bcoef border bias; boundary points are 6% of the grid but 23-45% of the
>1px MATLAB-vs-cppxdic disagreements, and cppxdic is the more correct side
there.
* docs: CLAUDE.md for the cuncorr-integration branch family
Cluster specifics (Manneback/Lemaitre path aliasing, purgeable
globalscratch, sacct job-id recycling trap), cpugpu harness usage,
config-file placement traps, and the July-2026 findings: CPU/GPU
perf + bit-parity, MATLAB agreement decomposition, the cuNCorr
matching limitation, and the Step-E/F bug chain.
* docs(study): definitive pipeline validation results
- t7/t12/t25 full pipeline (D+E+F) pass rc=0 on both engines
(GPU 32-38 min, ncorr CPU-32t 58-75 min).
- MATLAB Step-D interop: cppxdic E+F on MATLAB's own 2D results
reproduces MATLAB's E exactly (Points3D max 3.2e-14, corrComb 0,
same 6730 points) and F per-point quantities to <0.05
(faces +0.23% from the zipping tail).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds cuNCorr (GPU-accelerated DIC engine, new submodule
Tools/cuNCorr) as a selectable DIC backend. cuNCorr runs on CUDA when a device is present and on its CPU backend otherwise — the two are numerically identical by construction (shared math cores; cuNCorr's owncuda_parity_testgates CUDA==CPU). CppNCorr remains the parity oracle underneath.Dependency chain:
cppxdic → cuncorr(engine) andcppxdic → ncorr(result types + legacy path).What changed
Tools/cuNCorr(@a9518ad); CMake builds the standalonecuncorrlib (its bundled CppNCorr, tests, and apps disabled — cppxdic keeps its ownncorrtarget). Configure-DCUNCORR_BACKEND=CUDAon a GPU node.src/dic/cuncorr_dic.{h,cpp}(cppxdic::run_cuncorr_dic): runs cuNCorr in sequence mode (fixed reference = frame 0, each frame warm-started from the previous) and packages the result asncorr::DIC_analysis_output— raw Lagrangian, pixel-unit, on the reduced grid. Grids align exactly with ncorr ((dim-1)/sf+1 == ceil(dim/sf); reduced node(r,c)↔ full pixel(r·sf, c·sf)), so the.binand StepE 3D reconstruction consume it unchanged.single_dic_workflow.cpp,step_d_workflow.cpp::runNcorrAnalysis) on new configdic_engine(cuncorrdefault |ncorrlegacy) +cuncorr_seed_search.tests/cuncorr_adapter_test.cpp(opt-in-DBUILD_CUNCORR_ADAPTER_TEST=ON): synthetic translation recovery through the adapter.dic_engine = cuncorrchanges production DIC numbers. cuNCorr is a different algorithm from CppNCorr (bicubic IC-GN vs biquintic RG-DIC), so results differ. Validate on real fingertip data before merging/relying on it. Reverting is one config line (dic_engine = ncorr).cuncorrlib and cppxdic keeps its ownncorr(same commit861b5ce), rather than re-sourcingncorrthrough cuNCorr's nested submodule — avoids a duplicatencorrtarget and include re-plumbing, with identical runtime behaviour.Verification
cppxdic,singledic, andcuncorr_adapter_testall build & link againstcuncorr+ncorr(CPU).ROI2D::form_unioncannot throw (mask built at the reduced ROI's exact dims).protobufdylib blocks all OpenCV-linked binaries — same asdump_oracle); runs on the cluster / any working-OpenCV host.Build
🤖 Generated with Claude Code