|
8 | 8 | # * Applies a random seed |
9 | 9 | # * Supports overriding memory estimates via a log file and a buffer |
10 | 10 | # * Adds quotation marks around strings in dotfiles |
| 11 | +# * Adds methods for cross-graph connections |
11 | 12 |
|
12 | 13 | # ORIGINAL WORK'S ATTRIBUTION NOTICE: |
13 | 14 | # Copyright (c) 2009-2016, Nipype developers |
|
50 | 51 | for Nipype's documentation. |
51 | 52 | """ # pylint: disable=line-too-long |
52 | 53 |
|
| 54 | +from collections.abc import Mapping |
53 | 55 | from copy import deepcopy |
54 | 56 | from inspect import Parameter, Signature, signature |
55 | 57 | import os |
56 | 58 | import re |
57 | | -from typing import Any, ClassVar, Optional |
| 59 | +from typing import Any, ClassVar, Optional, TYPE_CHECKING |
58 | 60 |
|
59 | 61 | from numpy import prod |
60 | 62 | from traits.trait_base import Undefined |
61 | 63 | from traits.trait_handlers import TraitListObject |
62 | 64 | from nibabel import load |
| 65 | +from nipype.interfaces.base.support import InterfaceResult |
63 | 66 | from nipype.interfaces.utility import Function |
64 | 67 | from nipype.pipeline import engine as pe |
65 | 68 | from nipype.pipeline.engine.utils import ( |
|
76 | 79 |
|
77 | 80 | from CPAC.utils.monitoring import getLogger, WFLOGGER |
78 | 81 |
|
| 82 | +if TYPE_CHECKING: |
| 83 | + pass |
| 84 | + |
79 | 85 | # set global default mem_gb |
80 | 86 | DEFAULT_MEM_GB = 2.0 |
81 | 87 | UNDEFINED_SIZE = (42, 42, 42, 1200) |
@@ -527,6 +533,25 @@ def __init__(self, name, base_dir=None, debug=False): |
527 | 533 | self._nodes_cache = set() |
528 | 534 | self._nested_workflows_cache = set() |
529 | 535 |
|
| 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 | + |
530 | 555 | def _configure_exec_nodes(self, graph): |
531 | 556 | """Ensure that each node knows where to get inputs from.""" |
532 | 557 | for node in graph.nodes(): |
@@ -565,6 +590,20 @@ def _configure_exec_nodes(self, graph): |
565 | 590 | except (FileNotFoundError, KeyError, TypeError): |
566 | 591 | self._handle_just_in_time_exception(node) |
567 | 592 |
|
| 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 | + |
568 | 607 | def _get_dot( |
569 | 608 | self, prefix=None, hierarchy=None, colored=False, simple_form=True, level=0 |
570 | 609 | ): |
@@ -678,6 +717,22 @@ def _get_dot( |
678 | 717 | WFLOGGER.debug("cross connection: %s", dotlist[-1]) |
679 | 718 | return ("\n" + prefix).join(dotlist) |
680 | 719 |
|
| 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 | + |
681 | 736 | def _handle_just_in_time_exception(self, node): |
682 | 737 | # pylint: disable=protected-access |
683 | 738 | if hasattr(self, "_local_func_scans"): |
|
0 commit comments