Skip to content

Commit 253ca80

Browse files
committed
🔀 Merge changes from #2160
1 parent ae5be2c commit 253ca80

8 files changed

Lines changed: 438 additions & 334 deletions

File tree

CPAC/longitudinal/wf/anat.py

Lines changed: 224 additions & 194 deletions
Large diffs are not rendered by default.

CPAC/pipeline/cpac_pipeline.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import sys
2626
import time
2727
from time import strftime
28+
from typing import Literal, Optional
2829

2930
import yaml
3031
import nipype
@@ -130,7 +131,7 @@
130131
# pylint: disable=wrong-import-order
131132
from CPAC.pipeline import nipype_pipeline_engine as pe
132133
from CPAC.pipeline.check_outputs import check_outputs
133-
from CPAC.pipeline.engine import initiate_rpool, NodeBlock
134+
from CPAC.pipeline.engine import initiate_rpool, NodeBlock, ResourcePool
134135
from CPAC.pipeline.nipype_pipeline_engine.plugins import (
135136
LegacyMultiProcPlugin,
136137
MultiProcPlugin,
@@ -162,15 +163,14 @@
162163
warp_deriv_mask_to_EPItemplate,
163164
warp_deriv_mask_to_T1template,
164165
warp_sbref_to_T1template,
165-
warp_T1mask_to_template,
166166
warp_timeseries_to_EPItemplate,
167167
warp_timeseries_to_T1template,
168168
warp_timeseries_to_T1template_abcd,
169169
warp_timeseries_to_T1template_dcan_nhp,
170170
warp_timeseries_to_T1template_deriv,
171171
warp_tissuemask_to_EPItemplate,
172172
warp_tissuemask_to_T1template,
173-
warp_wholeheadT1_to_template,
173+
warp_to_template,
174174
)
175175
from CPAC.reho.reho import reho, reho_space_template
176176
from CPAC.sca.sca import dual_regression, multiple_regression, SCA_AVG
@@ -1061,25 +1061,30 @@ def build_anat_preproc_stack(rpool, cfg, pipeline_blocks=None):
10611061
return pipeline_blocks
10621062

10631063

1064-
def build_T1w_registration_stack(rpool, cfg, pipeline_blocks=None):
1064+
def build_T1w_registration_stack(
1065+
rpool: ResourcePool,
1066+
cfg: Configuration,
1067+
pipeline_blocks: Optional[list] = None,
1068+
space: Literal["longitudinal", "T1w"] = "T1w",
1069+
):
10651070
"""Build the T1w registration pipeline blocks."""
10661071
if not pipeline_blocks:
10671072
pipeline_blocks = []
10681073

10691074
reg_blocks = []
1070-
if not rpool.check_rpool("from-T1w_to-template_mode-image_xfm"):
1075+
if not rpool.check_rpool(f"from-{space}_to-template_mode-image_xfm"):
10711076
reg_blocks = [
10721077
[register_ANTs_anat_to_template, register_FSL_anat_to_template],
10731078
overwrite_transform_anat_to_template,
1074-
warp_wholeheadT1_to_template,
1075-
warp_T1mask_to_template,
1079+
warp_to_template("wholehead", space),
1080+
warp_to_template("mask", space),
10761081
]
10771082

10781083
if not rpool.check_rpool("desc-restore-brain_T1w"):
10791084
reg_blocks.append(correct_restore_brain_intensity_abcd)
10801085

10811086
if cfg.voxel_mirrored_homotopic_connectivity["run"]:
1082-
if not rpool.check_rpool("from-T1w_to-symtemplate_mode-image_xfm"):
1087+
if not rpool.check_rpool(f"from-{space}_to-symtemplate_mode-image_xfm"):
10831088
reg_blocks.append(
10841089
[
10851090
register_symmetric_ANTs_anat_to_template,

CPAC/pipeline/cpac_runner.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ def run_cpac_on_cluster(config_file, subject_list_file, cluster_files_dir):
236236
f.write(pid)
237237

238238

239-
def run_T1w_longitudinal(sublist, cfg):
239+
def run_T1w_longitudinal(sublist, cfg: Configuration, dry_run: bool = False):
240240
subject_id_dict = {}
241241

242242
for sub in sublist:
@@ -249,7 +249,7 @@ def run_T1w_longitudinal(sublist, cfg):
249249
# sessions for each participant as value
250250
for subject_id, sub_list in subject_id_dict.items():
251251
if len(sub_list) > 1:
252-
anat_longitudinal_wf(subject_id, sub_list, cfg)
252+
anat_longitudinal_wf(subject_id, sub_list, cfg, dry_run=dry_run)
253253
elif len(sub_list) == 1:
254254
warnings.warn(
255255
"\n\nThere is only one anatomical session "
@@ -495,7 +495,7 @@ def run(
495495
hasattr(c, "longitudinal_template_generation")
496496
and c.longitudinal_template_generation["run"]
497497
):
498-
run_T1w_longitudinal(sublist, c)
498+
run_T1w_longitudinal(sublist, c, dry_run=test_config)
499499
# TODO functional longitudinal pipeline
500500

501501
"""

CPAC/pipeline/engine.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,7 +1229,11 @@ def gather_pipes(self, wf, cfg, all=False, add_incl=None, add_excl=None):
12291229
unlabelled.remove(key)
12301230
# del all_forks
12311231
for pipe_idx in self.rpool[resource]:
1232-
pipe_x = self.get_pipe_number(pipe_idx)
1232+
try:
1233+
pipe_x = self.get_pipe_number(pipe_idx)
1234+
except ValueError:
1235+
# already gone
1236+
continue
12331237
json_info = self.rpool[resource][pipe_idx]["json"]
12341238
out_dct = self.rpool[resource][pipe_idx]["out"]
12351239

@@ -2623,7 +2627,14 @@ def _set_nested(attr, keys):
26232627
return wf, rpool
26242628

26252629

2626-
def initiate_rpool(wf, cfg, data_paths=None, part_id=None):
2630+
def initiate_rpool(
2631+
wf: pe.Workflow,
2632+
cfg: Configuration,
2633+
data_paths=None,
2634+
part_id=None,
2635+
*,
2636+
rpool: Optional[ResourcePool] = None,
2637+
):
26272638
"""
26282639
Initialize a new ResourcePool.
26292640
@@ -2662,7 +2673,7 @@ def initiate_rpool(wf, cfg, data_paths=None, part_id=None):
26622673
unique_id = part_id
26632674
creds_path = None
26642675

2665-
rpool = ResourcePool(name=unique_id, cfg=cfg)
2676+
rpool = ResourcePool(rpool=rpool.rpool if rpool else None, name=unique_id, cfg=cfg)
26662677

26672678
if data_paths:
26682679
# ingress outdir

CPAC/pipeline/nipype_pipeline_engine/engine.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# * Applies a random seed
99
# * Supports overriding memory estimates via a log file and a buffer
1010
# * Adds quotation marks around strings in dotfiles
11+
# * Adds methods for cross-graph connections
1112

1213
# ORIGINAL WORK'S ATTRIBUTION NOTICE:
1314
# Copyright (c) 2009-2016, Nipype developers
@@ -50,16 +51,18 @@
5051
for Nipype's documentation.
5152
""" # pylint: disable=line-too-long
5253

54+
from collections.abc import Mapping
5355
from copy import deepcopy
5456
from inspect import Parameter, Signature, signature
5557
import os
5658
import re
57-
from typing import Any, ClassVar, Optional
59+
from typing import Any, ClassVar, Optional, TYPE_CHECKING
5860

5961
from numpy import prod
6062
from traits.trait_base import Undefined
6163
from traits.trait_handlers import TraitListObject
6264
from nibabel import load
65+
from nipype.interfaces.base.support import InterfaceResult
6366
from nipype.interfaces.utility import Function
6467
from nipype.pipeline import engine as pe
6568
from nipype.pipeline.engine.utils import (
@@ -76,6 +79,9 @@
7679

7780
from CPAC.utils.monitoring import getLogger, WFLOGGER
7881

82+
if TYPE_CHECKING:
83+
pass
84+
7985
# set global default mem_gb
8086
DEFAULT_MEM_GB = 2.0
8187
UNDEFINED_SIZE = (42, 42, 42, 1200)
@@ -527,6 +533,25 @@ def __init__(self, name, base_dir=None, debug=False):
527533
self._nodes_cache = set()
528534
self._nested_workflows_cache = set()
529535

536+
def copy_input_connections(self, node1: pe.Node, node2: pe.Node) -> None:
537+
"""Copy input connections from ``node1`` to ``node2``."""
538+
new_connections: list[tuple[pe.Node, str, pe.Node, str]] = []
539+
for connection in self._graph.edges:
540+
_out: pe.Node
541+
_in: pe.Node
542+
_out, _in = connection
543+
if _in == node1:
544+
details = self._graph.get_edge_data(*connection)
545+
if "connect" in details:
546+
for connect in details["connect"]:
547+
new_connections.append((_out, connect[0], node2, connect[1]))
548+
for connection in new_connections:
549+
try:
550+
self.connect(*connection)
551+
except Exception:
552+
# connection already exists
553+
continue
554+
530555
def _configure_exec_nodes(self, graph):
531556
"""Ensure that each node knows where to get inputs from."""
532557
for node in graph.nodes():
@@ -565,6 +590,20 @@ def _configure_exec_nodes(self, graph):
565590
except (FileNotFoundError, KeyError, TypeError):
566591
self._handle_just_in_time_exception(node)
567592

593+
def _connect_node_or_path(
594+
self,
595+
node: pe.Node,
596+
strats_dct: Mapping[str, list[tuple[pe.Node, str] | str]],
597+
key: str,
598+
index: int,
599+
) -> None:
600+
"""Set input appropriately for either a Node or a path string."""
601+
_input: str = f"in{index + 1}"
602+
if isinstance(strats_dct[key][index], str):
603+
setattr(node.inputs, _input, strats_dct[key][index])
604+
else:
605+
self.connect(*strats_dct[key][index], node, _input)
606+
568607
def _get_dot(
569608
self, prefix=None, hierarchy=None, colored=False, simple_form=True, level=0
570609
):
@@ -678,6 +717,22 @@ def _get_dot(
678717
WFLOGGER.debug("cross connection: %s", dotlist[-1])
679718
return ("\n" + prefix).join(dotlist)
680719

720+
def get_output_path(self, node: pe.Node, out: str) -> str:
721+
"""Get an output path from an already-run Node."""
722+
try:
723+
_run_node: pe.Node = next(
724+
iter(
725+
_
726+
for _ in self.run(updatehash=True).nodes
727+
if _.fullname == node.fullname
728+
)
729+
)
730+
except IndexError as index_error:
731+
msg = f"Could not find {node.fullname} in {self}'s run Nodes."
732+
raise LookupError(msg) from index_error
733+
_res: InterfaceResult = _run_node.run()
734+
return getattr(_res.outputs, out)
735+
681736
def _handle_just_in_time_exception(self, node):
682737
# pylint: disable=protected-access
683738
if hasattr(self, "_local_func_scans"):

0 commit comments

Comments
 (0)