Fix CVM inference path (restore branch, reference table, tag collision) - #10
Fix CVM inference path (restore branch, reference table, tag collision)#10Hahyun-Lee wants to merge 2 commits into
Conversation
Restores SPARE-CVM inference and fixes three defects that prevented the CVM models from running. Verified on 20 subjects x 5 CVM models (T2D, HYPERTENSION, OBESITY, SMOKING, HYPERLIPIDEMIA); outputs match a manually patched run 100/100. svm.py - Restore the CVM inference branch. It is present at 38cfe71 (the commit the published cbica/nichart_spare_score_runall:0.0.4 image is built from) but absent from main, so `-t CVM` currently falls through and X is unbound. - Keep Age/Sex/ICV through the column subset for CVM-family models. They are consumed by apply_cvm_residualization() after the subset, so dropping them raised KeyError: 'Age'. - Add age_col/sex_col/icv_col parameters, defaulting to the values already hardcoded elsewhere in the module. data_prep.py - Resolve the residualization reference table from the file that actually ships (covparams_scaler_sparecvms_dl.csv). The hardcoded '..._dl2.csv' is not in the repo, so CVM inference died with FileNotFoundError. Prefers _dl2 when present, and raises an explicit error listing both candidates. - Map the ICV row of the reference table to 'DLICV', matching the rename applied to the input frame a few lines above. Without it the lookup returns an empty array and residualization fails with "operands could not be broadcast together with shapes (n,) (0,)". scripts/run_all.py - Keep the condition in the CVM tag (CVM-T2D, CVM-HYPERTENSION, ...). extract_spare_tag() returned 'CVM' for all five models, so four of them hit the duplicate-tag check and were silently skipped. Refs CBICA#8
There was a problem hiding this comment.
Pull request overview
Restores and fixes the CVM-family inference path so CVM models can run on main, including residualization reference-table handling and improved model tagging in run_all to avoid collisions.
Changes:
- Add CVM-family inference branch in
infer_svm_model()and preserve Age/Sex/ICV through subsetting for residualization. - Make
apply_cvm_residualization()robust to the shipped reference filename (*_dl.csv) while preferring*_dl2.csvwhen present, and fix ICV feature-name mismatch. - Improve
run_all.pytag extraction for CVM models to avoid duplicate-tag skipping.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| scripts/run_all.py | Adjusts tag extraction to distinguish CVM condition models. |
| NiChart_SPARE/svm.py | Restores CVM-family inference path and preserves demographics/ICV for residualization. |
| NiChart_SPARE/data_prep.py | Fixes CVM residualization reference-file selection and aligns ICV feature naming with the table. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| m = re.search(r"SPARE-(CVM-[A-Za-z0-9]+)-", name) | ||
| if m: | ||
| return m.group(1) | ||
| m = re.search(r"SPARE-([A-Za-z0-9]+)-", name) | ||
| return m.group(1) if m else None |
| if spare_type in _cvm_types: | ||
| for _c in (age_col, sex_col, icv_col): | ||
| if _c in df.columns and _c not in _keep: | ||
| _keep.append(_c) | ||
| df = df[_keep + meta_data['training_data_description']['feature_names']] |
Addresses both review comments, and fixes a pre-existing crash found while
verifying them. Verified end-to-end through run_all.py this time (the first
round only exercised NiChart_SPARE directly, which is how the column collision
and the NameError were missed).
run_all.py
- dlicv_found was referenced but never assigned, so run_all.py raised
NameError before reaching model selection - for every --category, not just
cvm. Assign it from the DLICV column check it was clearly meant to report on.
- Fix the missing f-prefix on the adjacent column-listing print, which emitted
the literal "{df_original.columns}".
- rename_spare_columns() only remapped SPARE_CL/SPARE_RG, so CVM-type models
(spare_type 'CVM' -> column SPARE_CVM) kept identical column names and
collided as _x/_y on merge, exactly as noted in review. Rename any
SPARE_<mode> column to SPARE_<tag> instead of enumerating modes, and carry
GT_BA alongside GT_RG.
svm.py
- Validate the CVM residualization inputs (Age/Sex/ICV) in the same
"Missing columns:" check as feature_names, so a missing one reports
consistently instead of surfacing as a bare KeyError inside
apply_cvm_residualization(). Per review.
Verification (20 subjects, aarch64, scikit-learn 1.9.0, reference dir left as
the repo ships it):
- run_all.py --category cvm --harmonize: rc=0, 5/5 models run, 0 duplicate-tag
skips, 0 _x/_y columns. Output columns: SPARE_CVM-T2D,
SPARE_CVM-HYPERTENSION(+_decision_function), SPARE_CVM-OBESITY(+df),
SPARE_CVM-HYPERLIPIDEMIA, SPARE_CVM-SMOKING(+df).
- run_all.py --category misc --harmonize: rc=0, 2/2 models, columns SPARE_AD,
SPARE_AD_decision_function, SPARE_BA, BA_GT_RG.
- Values identical to per-model direct invocation: CVM 100/100 cells, SPARE-BA
20/20, SPARE-AD 20/20.
- Missing-column path: dropping Age now yields "Error: Missing columns:Age".
|
Both comments were correct — fixed in 8b4d79a, and chasing them surfaced a pre-existing crash as well. 1. 2. Up-front validation for the CVM residualization columns — done. 3. Pre-existing: Verifying the two comments above meant running
Verification — this time end-to-end through
CVM output columns: Values are identical to per-model direct invocation — CVM 100/100 cells, SPARE-BA 20/20, SPARE-AD 20/20 — so the tag/rename change doesn't alter any prediction. One open question from the original description still stands: if |
Summary
SPARE-CVM inference does not run on
main. This PR restores the CVM branch and fixes three defects that stopped the CVM models even where the branch exists. Related to #8, which observes that inference only handlesRGandCL.Verified end-to-end on 20 subjects × 5 CVM models (T2D, HYPERTENSION, OBESITY, SMOKING, HYPERLIPIDEMIA). All five produce output, and values match a manually patched run 100/100.
What was wrong
I hit these while running the published
cbica/nichart_spare_score_runall:0.0.4image and then reproducing againstmain.1. The CVM inference branch is missing from
main.infer_svm_model()handlesRGandCLonly, so-t CVMfalls through every branch andXis unbound. The branch does exist at38cfe719— the commit the published0.0.4image is built from — andmainis 6 commits ahead of it, so this looks like it was dropped along the way rather than never written. This PR restores it.2. The residualization reference table is not the one that ships.
apply_cvm_residualization()readsreference/covparams_scaler_sparecvms_dl2.csv, but the repo shipscovparams_scaler_sparecvms_dl.csv:The patch prefers
_dl2when present and otherwise falls back to the packaged_dl, raising an explicit error naming both if neither exists. If_dl2is meant to be a different table, shipping it would be the better fix — please say so and I'll switch this to a plain filename correction.3. The ICV row of the reference table is looked up under the wrong name.
The input frame is renamed
DL_MUSE_Volume_702→DLICV, but the reference table still lists that row asDL_MUSE_Volume_702. The lookup returns an empty array:The commented-out
df_params.rename(columns={'DLICV': dlicv_col})right below the read suggests this was known. The patch maps theFeaturesvalue instead of the column, which is where the mismatch actually is.4.
Age/Sex/ICVare dropped before the CVM path needs them.The column subset keeps only
key_variable+feature_names, butapply_cvm_residualization()is called after it and requiresAge,Sex, and the ICV column:The patch keeps those three for CVM-family
spare_types only, soRG/CL/ADbehaviour is unchanged.5. All five CVM models collapse to one tag.
extract_spare_tag()matchesSPARE-([A-Za-z0-9]+)-, which returnsCVMfor everySPARE-CVM-<CONDITION>-Harmonized.joblib. Four of the five then hit the duplicate-tag check inrun_all.pyand are skipped with onlySkipping due to duplicate spare tag...in the log — no error, exit code 0. The patch keeps the condition (CVM-T2D,CVM-HYPERTENSION, …) so each model gets its own output column.Verification
Run on an aarch64 host (DGX Spark GB10), scikit-learn 1.9.0, against this branch with the reference directory left exactly as the repo ships it:
-tTag uniqueness after the change: 7/7 distinct across the five CVM models plus
SPARE-BA-RAW-*andSPARE-AD-Harmonized-*(previously 3/7).RG/CL/ADpaths are untouched apart from the subset guard, which is gated onspare_type.Notes / open questions
-thas to match the model'smeta_data['spare_type']—CVMfor T2D and HYPERLIPIDEMIA,CLfor the other three. That is not obvious from the filenames; happy to add a note to the README if useful.Sex_Mone-hot column for theCLCVM models.apply_cvm_residualization()derives it when missing, but theCLpath does not go through that function, so those three fail withMissing columns:Sex_M. Left alone here since it may be intended, but flagging it.