[ENH] Fix registration + use mask apply in tractoflow#369
Conversation
gagnonanthony
left a comment
There was a problem hiding this comment.
Great job! I left a few comments. Also, I'm trying to figure out what someone would expect as output when supplying images with their associated masks. I would assume they don't really want the masked registered images, but probably the original images warped using the mask, no? This means we should probably add an antsApplyTransforms at the end to warp the moving original (non-masked) image. This is just an opinion and I'm not a power user of this subworkflow, so feel free to chip in!
AlexVCaron
left a comment
There was a problem hiding this comment.
Sorry, I missed your message on the merged PR. I was not advocating for prior masking as a replacement to feeding the masks to the registration. As @gdevenyi said, neither one or the other has proven stable really, and both have their use-cases.
I'd rather have both options (including their combination). versaFlow is an example of this. Its registration chain from T1 to DWI needs to configure antsRegistration under all 3 cases to work correctly (and more, but let's not get into that).
|
Ok I stop coding until we agree on the strategy here is the link to what @gdevenyi said: #367 (comment). I think to add an extra layer of masking within or before becomes complicated but maybe I'm getting lazy. |
I think the current implementation is okay for now, we can extend the conversation at the SIG on Thursday and decide on the matter. I'm not against complexity, registration is an unstable and complex workflow. Aside healthy adult human brains, it's non-trivial and requires deep fine-tuning. I'm happy to put my hand in the fryer on this one and contribute to the implementation, as I've done much of what I ask for in versaFlow. |
|
I think the safer thing to do here is to add a "mask extraction" option in each of the individual registration tools so that we only have to pass around the full unmasked images. My registration tool already has this as an option internally (--mask-extract) so its easy for me to implement. |
|
@gdevenyi, @AlexVCaron , @gagnonanthony |
| out_ref_warped_masked = out_ref_warped | ||
| .join(ch_fixed_mask) | ||
| .filter{ _meta, _warped, mask -> mask } | ||
| .map{ meta, warped, _mask -> [meta, warped] } | ||
|
|
There was a problem hiding this comment.
Audit: Invalid for the current code. For the ANTs path, out_ref_warped is populated from REGISTRATION_ANTS.out.fixed_warped (*_warped_reference.nii.gz) before out_ref_warped_masked captures it — the subworkflow test snapshot confirms reference_warped_masked emits test_warped_reference.nii.gz for the SyNQuick test (which has a fixed mask). Same for SynthMorph (REGISTRATION_SYNTHMORPH.out.fixed_warped). The channel is not empty at the point out_ref_warped_masked is derived.
| emit: | ||
| image_warped = out_image_warped // channel: [ val(meta), image ] | ||
| reference_warped = out_ref_warped // channel: [ val(meta), ref ] | ||
| image_warped_masked = out_image_warped_masked // channel: [ val(meta), image ] | ||
| reference_warped_masked = out_ref_warped_masked // channel: [ val(meta), ref ] |
There was a problem hiding this comment.
Audit: Still valid at HEAD (84f37ce). meta.yml does not document image_warped_masked or reference_warped_masked outputs, and reference_warped still states "ONLY PROVIDED BY REGISTRATION_EASYREG" despite now being produced by all registration paths (ANTS, SynthMorph via WARP_IMAGE_TO_MOVING).
| if ( ( options.masking_strategy == "apriori" || options.masking_strategy == "both" ) && ( ch_fixed_mask || ch_moving_mask || ch_metric ) ) { | ||
| if ( ch_fixed_mask ) { | ||
| MASK_FIXED_IMAGE ( ch_fixed_image.join(ch_fixed_mask) ) | ||
| ch_fixed_image_ready = ch_fixed_image.join(MASK_FIXED_IMAGE.out.image, remainder: true) | ||
| .map({ meta, orig, masked -> [meta, masked?: orig] }) |
There was a problem hiding this comment.
Audit: Partially addressed. The subworkflow test config sets ext.masking_strategy = "apriori" for REGISTRATION_ANTS, and the SyNQuick test passes a fixed mask — so apriori with a fixed mask is exercised. However, no test covers 'both', 'internal', or the combination of fixed + moving masks. The ch_fixed_metric_ready bug (metric + no fixed mask + apriori) would not be caught by the current suite.
…ithub.com:arnaudbore/nf-neuro into move_mask_from_registration_module_to_subworkflow
| .branch{ | ||
| anat_to_dwi : it[3] | ||
| ants_syn: true | ||
| return it[0..2] + it[4..5] | ||
| } |
There was a problem hiding this comment.
Audit: Invalid. Nextflow's branch operator evaluates predicates sequentially — anat_to_dwi: it[3] captures metric-bearing items first; ants_syn: true is a catch-all default for items that didn't match the prior branch. Items do not route to both branches. An item with a truthy it[3] (metric) goes to anat_to_dwi only.
| UTILS_OPTIONS("${moduleDir}/meta.yml", options, true) | ||
| options = UTILS_OPTIONS.out.options.value | ||
|
|
||
| if ( ( options.masking_strategy == "apriori" || options.masking_strategy == "both" ) && ( ch_fixed_mask || ch_moving_mask || ch_metric ) ) { |
There was a problem hiding this comment.
Audit: Valid and still present at HEAD. This is the same root issue as the earlier comment on line 69. ch_fixed_metric_ready is undefined in the inner else (no fixed mask) when masking_strategy is apriori/both. Trigger: masking_strategy = "apriori" + metric supplied + no fixed mask → MissingPropertyException. Needs fixing.
There was a problem hiding this comment.
@arnaudbore I think this still needs addressing?
| moving_base=\$(basename "${moving_anat}") | ||
| ext=\${moving_base#*.} | ||
| moving_id=\${moving_base%.\${ext}} | ||
| moving_id=\$(basename $moving_anat .nii.gz) |
There was a problem hiding this comment.
Audit: Valid concern, but pre-existing — the basename $moving_anat .nii.gz hardcoding was in the original code before this PR (not a regression introduced here). The REGISTRATION_ANTS module handles this more robustly with ext=${moving_base#*.} / moving_id=${moving_base%.${ext}}. Worth aligning anattodwi to the same pattern for consistency, but it can be a follow-up.
PR #369 Review — Registration masking move to subworkflowOverall the architecture is sound: masking concerns move out of individual modules into the subworkflow, original unmasked images are re-warped post-registration via 🔴 Blocker:
|
| Location | Issue |
|---|---|
subworkflows/.../registration/meta.yml |
image_warped_masked and reference_warped_masked are emitted but not documented in outputs |
subworkflows/.../registration/meta.yml |
reference_warped still says "ONLY PROVIDED BY REGISTRATION_EASYREG" — now produced by all paths (ANTS, SynthMorph via WARP_IMAGE_TO_MOVING) |
modules/.../ants/meta.yml + anattodwi/meta.yml |
masking_strategy choices list 'none', 'apriori', 'both' — missing 'internal' which the code checks |
subworkflows/.../registration/meta.yml:99,112 |
Grammar: "will be use" → "will be used" |
🟡 Minor
-
applymask/main.nf:19— stray leading space in the else branch produces a double space in themrcalccommand:def data_type = task.ext.data_type ? "-datatype ${task.ext.data_type}" : " -datatype float32" // ^ extra leading space
-
No subworkflow test covers
masking_strategy: 'both'or'internal', nor any test with both fixed AND moving masks. The current SyNQuick test exercises'apriori'with a fixed mask only. Thech_fixed_metric_readybug above would not be caught by the current suite — a regression test forapriori+ metric (anattodwi) + no fixed mask would catch it. -
Test assertion key mismatch (pre-existing):
["ref_warped", "segmentation", "ref_segmentation"]in the subworkflow test doesn't match the actual emit namesreference_warped/reference_segmentation, so the empty-check assertion is a no-op.
✅ What's good
- Clean separation: modules stay dumb, subworkflow orchestrates masking.
- Re-warping original unmasked images via
antsApplyTransformsis the right call (addresses @gagnonanthony's original feedback about wanting the original image warped, not the masked one). - Backward compatible via
masking_strategy = "none"default. - Module-level tests added for mask inputs (anattodwi fixed/moving mask tests, ants both-moving-mask config).
applymaskfirst_suffix/data_typeoptions are clean generalizations.
Recommendation: Request changes — fix the ch_fixed_metric_ready blocker + add a regression test, address the *_warped_masked semantics, and update the meta.yml docs.
|
I'll have additional comments after a review of the impact of this on #282 |
Align cobralab_ants with the registration subworkflow masking rework (PR nf-neuro#369): gate fixed/moving mask forwarding behind masking_strategy (none/apriori/internal/both), add fixed_warped output (reference warped to moving space via antsApplyTransforms), and update tests accordingly.
|
@arnaudbore can you update the PR description with a full description of the overall intention and design of the implementation |
Type of improvement
Masking for registration modules is done in registration subworkflow
If submitting a new module or fixing a bug, please use the appropriate template.
Describe your improvement
Write a clear and concise description of what the improvement is.
Describe how to test your improvement
Provide a full step-by-step guide to test your improvement.
Checklist before requesting a review