Skip to content

[ENH] Fix registration + use mask apply in tractoflow#369

Open
arnaudbore wants to merge 15 commits into
nf-neuro:mainfrom
arnaudbore:move_mask_from_registration_module_to_subworkflow
Open

[ENH] Fix registration + use mask apply in tractoflow#369
arnaudbore wants to merge 15 commits into
nf-neuro:mainfrom
arnaudbore:move_mask_from_registration_module_to_subworkflow

Conversation

@arnaudbore

Copy link
Copy Markdown
Contributor

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.

  • Documentation
  • Development tools (e.g. linter, formatter, etc.)
  • Development container
  • Global update (please specify)
  • Other (please specify)

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

  • Ensure the syntax is correct (EditorConfig and Prettier must pass)
  • Run the test suites if your changes affect any module
  • Regenerate the Poetry lock file if you have updated the dependencies
  • Ensure the documentation is up-to-date

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@gagnonanthony gagnonanthony left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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!

Comment thread subworkflows/nf-neuro/registration/main.nf Outdated
Comment thread subworkflows/nf-neuro/registration/main.nf Outdated
Comment thread subworkflows/nf-neuro/registration/main.nf Outdated
Comment thread subworkflows/nf-neuro/tractoflow/main.nf Outdated
Comment thread subworkflows/nf-neuro/tractoflow/modules.config Outdated

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

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).

@arnaudbore

Copy link
Copy Markdown
Contributor Author

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.

@AlexVCaron

Copy link
Copy Markdown
Contributor

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.

@arnaudbore arnaudbore changed the title [ENH] Fix registration + use mask apply in tractoflow [ENH][WIP] Fix registration + use mask apply in tractoflow Jun 4, 2026
@arnaudbore arnaudbore added the stale Issue that has not moved to be closed if no further discussions occurs after label assignment label Jun 4, 2026
@gdevenyi

gdevenyi commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

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.

@arnaudbore arnaudbore removed the stale Issue that has not moved to be closed if no further discussions occurs after label assignment label Jul 10, 2026
@arnaudbore arnaudbore changed the title [ENH][WIP] Fix registration + use mask apply in tractoflow [ENH] Fix registration + use mask apply in tractoflow Jul 10, 2026
@arnaudbore

Copy link
Copy Markdown
Contributor Author

@gdevenyi, @AlexVCaron , @gagnonanthony
Ready for a proper review. This needs to be clean. I'm triggering claude review.

Copilot AI 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.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 13 comments.

Comment thread subworkflows/nf-neuro/registration/main.nf
Comment thread subworkflows/nf-neuro/registration/main.nf Outdated
Comment thread subworkflows/nf-neuro/registration/main.nf
Comment thread subworkflows/nf-neuro/registration/main.nf Outdated
Comment thread subworkflows/nf-neuro/registration/main.nf Outdated
Comment thread subworkflows/nf-neuro/registration/meta.yml
Comment thread modules/nf-neuro/registration/ants/meta.yml
Comment thread modules/nf-neuro/registration/anattodwi/meta.yml
Comment thread modules/nf-neuro/registration/ants/main.nf Outdated
Comment thread modules/nf-neuro/registration/anattodwi/main.nf Outdated
@arnaudbore arnaudbore requested a review from Copilot July 11, 2026 16:27

Copilot AI 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.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 3 comments.

Comment on lines +275 to +279
out_ref_warped_masked = out_ref_warped
.join(ch_fixed_mask)
.filter{ _meta, _warped, mask -> mask }
.map{ meta, warped, _mask -> [meta, warped] }

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.

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.

Comment on lines 304 to +308
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 ]

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.

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).

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.

@arnaudbore still need to fix this

Comment on lines +41 to +45
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] })

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.

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.

Copilot AI 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.

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 3 comments.

Comment on lines 219 to 223
.branch{
anat_to_dwi : it[3]
ants_syn: true
return it[0..2] + it[4..5]
}

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.

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 ) ) {

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.

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.

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.

@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)

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.

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.

@gdevenyi

Copy link
Copy Markdown
Contributor

PR #369 Review — Registration masking move to subworkflow

Overall the architecture is sound: masking concerns move out of individual modules into the subworkflow, original unmasked images are re-warped post-registration via antsApplyTransforms, and the default masking_strategy = "none" keeps things backward compatible. CI is green (32/32). That said, there's one blocker-level bug and several items that should be addressed before merge.


🔴 Blocker: ch_fixed_metric_ready undefined in one code path

subworkflows/nf-neuro/registration/main.nf — in the apriori/both masking block:

if ( (masking_strategy == "apriori" || "both") && (ch_fixed_mask || ch_moving_mask || ch_metric) ) {
    if ( ch_fixed_mask ) {
        ...
        ch_fixed_metric_ready = ch_metric (or masked version)
    }
    else {
        ch_fixed_image_ready = ch_fixed_image
        // ← ch_fixed_metric_ready NEVER ASSIGNED here
    }
}

Trigger: masking_strategy = "apriori" (or "both") + a metric is supplied (anattodwi path) + no fixed mask. The variable is later referenced at .join(ch_fixed_metric_ready, ...)MissingPropertyException at runtime.

The outer else (for "none") correctly sets all three ready-channels; only this inner else is missing the assignment.

Fix: add ch_fixed_metric_ready = ch_metric to the inner else branch.


🟠 image_warped_masked / reference_warped_masked semantics

Two related issues:

  1. Mislabeled for internal strategy. With internal, ANTs receives masks via -x but emits unmasked warped images (the mask only constrains the metric, not the output). Yet out_image_warped_masked filters by mask presence and emits those unmasked images under a _masked label. This is only correct for apriori/both (where images are pre-masked before registration). Consider gating these outputs behind the masking strategy, or actually applying the mask to the warped output.

  2. Computed unconditionally. The *_warped_masked blocks run regardless of masking_strategy. With "none" + masks present, these channels emit unmasked images under a masked label. They should be guarded behind the strategy check (or the masks should actually be applied).


🟠 Documentation gaps

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 the mrcalc command:

    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. The ch_fixed_metric_ready bug above would not be caught by the current suite — a regression test for apriori + 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 names reference_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 antsApplyTransforms is 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).
  • applymask first_suffix / data_type options 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.

@gdevenyi gdevenyi 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 comment

@gdevenyi

Copy link
Copy Markdown
Contributor

I'll have additional comments after a review of the impact of this on #282

@arnaudbore arnaudbore requested a review from gdevenyi July 14, 2026 01:46
gdevenyi added a commit to gdevenyi/nf-neuro that referenced this pull request Jul 14, 2026
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.
@gdevenyi

Copy link
Copy Markdown
Contributor

@arnaudbore can you update the PR description with a full description of the overall intention and design of the implementation

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.

5 participants