{"action":"created","comment":{"url":"https://api.github.com/repos/nipy/nipype/pulls/comments/47843242","id":47843242,"diff_hunk":"@@ -0,0 +1,1096 @@\n+#!/usr/bin/env python\n+# -*- coding: utf-8 -*-\n+# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-\n+# vi: set ft=python sts=4 ts=4 sw=4 et:\n+\"\"\"Defines functionality for pipelined execution of interfaces\n+\n+The `Workflow` class provides core functionality for batch processing.\n+\n+   Change directory to provide relative paths for doctests\n+   >>> import os\n+   >>> filepath = os.path.dirname( os.path.realpath( __file__ ) )\n+   >>> datadir = os.path.realpath(os.path.join(filepath, '../testing/data'))\n+   >>> os.chdir(datadir)\n+\n+\"\"\"\n+\n+from future import standard_library\n+standard_library.install_aliases()\n+\n+from datetime import datetime\n+try:\n+    from collections import OrderedDict\n+except ImportError:\n+    from ordereddict import OrderedDict\n+\n+from copy import deepcopy\n+import pickle\n+import os\n+import os.path as op\n+import shutil\n+import sys\n+from warnings import warn\n+import numpy as np\n+import networkx as nx\n+\n+from nipype.interfaces.base import (traits, TraitedSpec, TraitDictObject, TraitListObject)\n+\n+from nipype.utils.misc import getsource, create_function_from_source\n+from nipype.utils.filemanip import save_json\n+from ..utils import (generate_expanded_graph, export_graph, make_output_dir,\n+                     write_workflow_prov, format_dot, topological_sort,\n+                     get_print_name, merge_dict)\n+\n+from nipype.external.six import string_types\n+from nipype import config, logging\n+from nipype.utils.misc import package_check, str2bool\n+\n+from .base import WorkflowBase\n+from .nodes import Node, MapNode\n+\n+logger = logging.getLogger('workflow')\n+package_check('networkx', '1.3')\n+\n+\n+class Workflow(WorkflowBase):\n+    \"\"\"Controls the setup and execution of a pipeline of processes.\"\"\"\n+\n+    def __init__(self, name, base_dir=None):\n+        \"\"\"Create a workflow object.\n+\n+        Parameters\n+        ----------\n+        name : alphanumeric string\n+            unique identifier for the workflow\n+        base_dir : string, optional\n+            path to workflow storage\n+\n+        \"\"\"\n+        super(Workflow, self).__init__(name, base_dir)\n+        self._graph = nx.DiGraph()\n+        self.config = deepcopy(config._sections)\n+\n+    # PUBLIC API\n+    def clone(self, name):\n+        \"\"\"Clone a workflow\n+\n+        .. note::\n+\n+          Will reset attributes used for executing workflow. See\n+          _init_runtime_fields.\n+\n+        Parameters\n+        ----------\n+\n+        name: alphanumeric name\n+            unique name for the workflow\n+\n+        \"\"\"\n+        clone = super(Workflow, self).clone(name)\n+        clone._reset_hierarchy()\n+        return clone\n+\n+    # Graph creation functions\n+    def connect(self, *args, **kwargs):\n+        \"\"\"Connect nodes in the pipeline.\n+\n+        This routine also checks if inputs and outputs are actually provided by\n+        the nodes that are being connected.\n+\n+        Creates edges in the directed graph using the nodes and edges specified\n+        in the `connection_list`.  Uses the NetworkX method\n+        DiGraph.add_edges_from.\n+\n+        Parameters\n+        ----------\n+\n+        args : list or a set of four positional arguments\n+\n+            Four positional arguments of the form::\n+\n+              connect(source, sourceoutput, dest, destinput)\n+\n+            source : nodewrapper node\n+            sourceoutput : string (must be in source.outputs)\n+            dest : nodewrapper node\n+            destinput : string (must be in dest.inputs)\n+\n+            A list of 3-tuples of the following form::\n+\n+             [(source, target,\n+                 [('sourceoutput/attribute', 'targetinput'),\n+                 ...]),\n+             ...]\n+\n+            Or::\n+\n+             [(source, target, [(('sourceoutput1', func, arg2, ...),\n+                                         'targetinput'), ...]),\n+             ...]\n+             sourceoutput1 will always be the first argument to func\n+             and func will be evaluated and the results sent ot targetinput\n+\n+             currently func needs to define all its needed imports within the\n+             function as we use the inspect module to get at the source code\n+             and execute it remotely\n+        \"\"\"\n+        if len(args) == 1:\n+            connection_list = args[0]\n+        elif len(args) == 4:\n+            connection_list = [(args[0], args[2], [(args[1], args[3])])]\n+        else:\n+            raise Exception('unknown set of parameters to connect function')\n+        if not kwargs:\n+            disconnect = False\n+        else:\n+            disconnect = kwargs['disconnect']\n+        newnodes = []\n+        for srcnode, destnode, _ in connection_list:\n+            if self in [srcnode, destnode]:\n+                msg = ('Workflow connect cannot contain itself as node:'\n+                       ' src[%s] dest[%s] workflow[%s]') % (srcnode,\n+                                                            destnode,\n+                                                            self.name)\n+\n+                raise IOError(msg)\n+            if (srcnode not in newnodes) and not self._has_node(srcnode):\n+                newnodes.append(srcnode)\n+            if (destnode not in newnodes) and not self._has_node(destnode):\n+                newnodes.append(destnode)\n+        if newnodes:\n+            logger.debug('New nodes: %s' % newnodes)\n+            self._check_nodes(newnodes)\n+            for node in newnodes:\n+                if node._hierarchy is None:\n+                    node._hierarchy = self.name\n+        not_found = []\n+        connected_ports = {}\n+        for srcnode, destnode, connects in connection_list:\n+            if destnode not in connected_ports:\n+                connected_ports[destnode] = []\n+            # check to see which ports of destnode are already\n+            # connected.\n+            if not disconnect and (destnode in self._graph.nodes()):\n+                for edge in self._graph.in_edges_iter(destnode):\n+                    data = self._graph.get_edge_data(*edge)\n+                    for sourceinfo, destname in data['connect']:\n+                        if destname not in connected_ports[destnode]:\n+                            connected_ports[destnode] += [destname]\n+            for source, dest in connects:\n+                # Currently datasource/sink/grabber.io modules\n+                # determine their inputs/outputs depending on\n+                # connection settings.  Skip these modules in the check\n+                if dest in connected_ports[destnode]:\n+                    raise Exception(\"\"\"\n+Trying to connect %s:%s to %s:%s but input '%s' of node '%s' is already\n+connected.\n+\"\"\" % (srcnode, source, destnode, dest, dest, destnode))\n+                if not (hasattr(destnode, '_interface') and\n+                        '.io' in str(destnode._interface.__class__)):\n+                    if not destnode._check_inputs(dest):\n+                        not_found.append(['in', '%s' % destnode, dest])\n+                if not (hasattr(srcnode, '_interface') and\n+                        '.io' in str(srcnode._interface.__class__)):\n+                    if isinstance(source, tuple):\n+                        # handles the case that source is specified\n+                        # with a function\n+                        sourcename = source[0]\n+                    elif isinstance(source, string_types):\n+                        sourcename = source\n+                    else:\n+                        raise Exception(('Unknown source specification in '\n+                                         'connection from output of %s') %\n+                                        srcnode.name)\n+                    if sourcename and not srcnode._check_outputs(sourcename):\n+                        not_found.append(['out', '%s' % srcnode, sourcename])\n+                connected_ports[destnode] += [dest]\n+        infostr = []\n+        for info in not_found:\n+            infostr += [\"Module %s has no %sput called %s\\n\" % (info[1],\n+                                                                info[0],\n+                                                                info[2])]\n+        if not_found:\n+            infostr.insert(\n+                0, 'Some connections were not found connecting %s.%s to '\n+                '%s.%s' % (srcnode, source, destnode, dest))\n+            raise Exception('\\n'.join(infostr))\n+\n+        # turn functions into strings\n+        for srcnode, destnode, connects in connection_list:\n+            for idx, (src, dest) in enumerate(connects):\n+                if isinstance(src, tuple) and not isinstance(src[1], string_types):\n+                    function_source = getsource(src[1])\n+                    connects[idx] = ((src[0], function_source, src[2:]), dest)\n+\n+        # add connections\n+        for srcnode, destnode, connects in connection_list:\n+            edge_data = self._graph.get_edge_data(srcnode, destnode, None)\n+            if edge_data:\n+                logger.debug('(%s, %s): Edge data exists: %s'\n+                             % (srcnode, destnode, str(edge_data)))\n+                for data in connects:\n+                    if data not in edge_data['connect']:\n+                        edge_data['connect'].append(data)\n+                    if disconnect:\n+                        logger.debug('Removing connection: %s' % str(data))\n+                        edge_data['connect'].remove(data)\n+                if edge_data['connect']:\n+                    self._graph.add_edges_from([(srcnode,\n+                                                 destnode,\n+                                                 edge_data)])\n+                else:\n+                    # pass\n+                    logger.debug('Removing connection: %s->%s' % (srcnode,\n+                                                                  destnode))\n+                    self._graph.remove_edges_from([(srcnode, destnode)])\n+            elif not disconnect:\n+                logger.debug('(%s, %s): No edge data' % (srcnode, destnode))\n+                self._graph.add_edges_from([(srcnode, destnode,\n+                                             {'connect': connects})])\n+            edge_data = self._graph.get_edge_data(srcnode, destnode)\n+            logger.debug('(%s, %s): new edge data: %s' % (srcnode, destnode,\n+                                                          str(edge_data)))\n+\n+    def disconnect(self, *args):\n+        \"\"\"Disconnect two nodes\n+\n+        See the docstring for connect for format.\n+        \"\"\"\n+        # yoh: explicit **dict was introduced for compatibility with Python 2.5\n+        return self.connect(*args, **dict(disconnect=True))\n+\n+    def add_nodes(self, nodes):\n+        \"\"\" Add nodes to a workflow\n+\n+        Parameters\n+        ----------\n+        nodes : list\n+            A list of WorkflowBase-based objects\n+        \"\"\"\n+        newnodes = []\n+        all_nodes = self._get_all_nodes()\n+        for node in nodes:\n+            if self._has_node(node):\n+                raise IOError('Node %s already exists in the workflow' % node)\n+            if isinstance(node, Workflow):\n+                for subnode in node._get_all_nodes():\n+                    if subnode in all_nodes:\n+                        raise IOError(('Subnode %s of node %s already exists '\n+                                       'in the workflow') % (subnode, node))\n+            newnodes.append(node)\n+        if not newnodes:\n+            logger.debug('no new nodes to add')\n+            return\n+        for node in newnodes:\n+            if not issubclass(node.__class__, WorkflowBase):\n+                raise Exception('Node %s must be a subclass of WorkflowBase' %\n+                                str(node))\n+        self._check_nodes(newnodes)\n+        for node in newnodes:\n+            if node._hierarchy is None:\n+                node._hierarchy = self.name\n+        self._graph.add_nodes_from(newnodes)\n+\n+    def remove_nodes(self, nodes):\n+        \"\"\" Remove nodes from a workflow\n+\n+        Parameters\n+        ----------\n+        nodes : list\n+            A list of WorkflowBase-based objects\n+        \"\"\"\n+        self._graph.remove_nodes_from(nodes)\n+\n+    # Input-Output access\n+    @property\n+    def inputs(self):\n+        return self._get_inputs()\n+\n+    @property\n+    def outputs(self):\n+        return self._get_outputs()\n+\n+    def get_node(self, name):\n+        \"\"\"Return an internal node by name\n+        \"\"\"\n+        nodenames = name.split('.')\n+        nodename = nodenames[0]\n+        outnode = [node for node in self._graph.nodes() if\n+                   str(node).endswith('.' + nodename)]\n+        if outnode:\n+            outnode = outnode[0]\n+            if nodenames[1:] and issubclass(outnode.__class__, Workflow):\n+                outnode = outnode.get_node('.'.join(nodenames[1:]))\n+        else:\n+            outnode = None\n+        return outnode\n+\n+    def list_node_names(self):\n+        \"\"\"List names of all nodes in a workflow\n+        \"\"\"\n+        outlist = []\n+        for node in nx.topological_sort(self._graph):\n+            if isinstance(node, Workflow):\n+                outlist.extend(['.'.join((node.name, nodename)) for nodename in\n+                                node.list_node_names()])\n+            else:\n+                outlist.append(node.name)\n+        return sorted(outlist)\n+\n+    def write_graph(self, dotfilename='graph.dot', graph2use='hierarchical',\n+                    format=\"png\", simple_form=True):\n+        \"\"\"Generates a graphviz dot file and a png file\n+\n+        Parameters\n+        ----------\n+\n+        graph2use: 'orig', 'hierarchical' (default), 'flat', 'exec', 'colored'\n+            orig - creates a top level graph without expanding internal\n+            workflow nodes;\n+            flat - expands workflow nodes recursively;\n+            hierarchical - expands workflow nodes recursively with a\n+            notion on hierarchy;\n+            colored - expands workflow nodes recursively with a\n+            notion on hierarchy in color;\n+            exec - expands workflows to depict iterables\n+\n+        format: 'png', 'svg'\n+\n+        simple_form: boolean (default: True)\n+            Determines if the node name used in the graph should be of the form\n+            'nodename (package)' when True or 'nodename.Class.package' when\n+            False.\n+\n+        \"\"\"\n+        graphtypes = ['orig', 'flat', 'hierarchical', 'exec', 'colored']\n+        if graph2use not in graphtypes:\n+            raise ValueError('Unknown graph2use keyword. Must be one of: ' +\n+                             str(graphtypes))\n+        base_dir, dotfilename = op.split(dotfilename)\n+        if base_dir == '':\n+            if self.base_dir:\n+                base_dir = self.base_dir\n+                if self.name:\n+                    base_dir = op.join(base_dir, self.name)\n+            else:\n+                base_dir = os.getcwd()\n+        base_dir = make_output_dir(base_dir)\n+        if graph2use in ['hierarchical', 'colored']:\n+            dotfilename = op.join(base_dir, dotfilename)\n+            self.write_hierarchical_dotfile(dotfilename=dotfilename,\n+                                            colored=graph2use == \"colored\",\n+                                            simple_form=simple_form)\n+            format_dot(dotfilename, format=format)\n+        else:\n+            graph = self._graph\n+            if graph2use in ['flat', 'exec']:\n+                graph = self._create_flat_graph()\n+            if graph2use == 'exec':\n+                graph = generate_expanded_graph(deepcopy(graph))\n+            export_graph(graph, base_dir, dotfilename=dotfilename,\n+                         format=format, simple_form=simple_form)\n+\n+    def write_hierarchical_dotfile(self, dotfilename=None, colored=False,\n+                                   simple_form=True):\n+        dotlist = ['digraph %s{' % self.name]\n+        dotlist.append(self._get_dot(prefix='  ', colored=colored,\n+                                     simple_form=simple_form))\n+        dotlist.append('}')\n+        dotstr = '\\n'.join(dotlist)\n+        if dotfilename:\n+            fp = open(dotfilename, 'wt')\n+            fp.writelines(dotstr)\n+            fp.close()\n+        else:\n+            logger.info(dotstr)\n+\n+    def export(self, filename=None, prefix=\"output\", format=\"python\",\n+               include_config=False):\n+        \"\"\"Export object into a different format\n+\n+        Parameters\n+        ----------\n+        filename: string\n+           file to save the code to; overrides prefix\n+        prefix: string\n+           prefix to use for output file\n+        format: string\n+           one of \"python\"\n+        include_config: boolean\n+           whether to include node and workflow config values\n+\n+        \"\"\"\n+        from utils import format_node\n+\n+        formats = [\"python\"]\n+        if format not in formats:\n+            raise ValueError('format must be one of: %s' % '|'.join(formats))\n+        flatgraph = self._create_flat_graph()\n+        nodes = nx.topological_sort(flatgraph)\n+\n+        lines = ['# Workflow']\n+        importlines = ['from nipype.pipeline.engine import Workflow, '\n+                       'Node, MapNode']\n+        functions = {}\n+        if format == \"python\":\n+            connect_template = '%s.connect(%%s, %%s, %%s, \"%%s\")' % self.name\n+            connect_template2 = '%s.connect(%%s, \"%%s\", %%s, \"%%s\")' \\\n+                                % self.name\n+            wfdef = '%s = Workflow(\"%s\")' % (self.name, self.name)\n+            lines.append(wfdef)\n+            if include_config:\n+                lines.append('%s.config = %s' % (self.name, self.config))\n+            for idx, node in enumerate(nodes):\n+                nodename = node.fullname.replace('.', '_')\n+                # write nodes\n+                nodelines = format_node(node, format='python',\n+                                        include_config=include_config)\n+                for line in nodelines:\n+                    if line.startswith('from'):\n+                        if line not in importlines:\n+                            importlines.append(line)\n+                    else:\n+                        lines.append(line)\n+                # write connections\n+                for u, _, d in flatgraph.in_edges_iter(nbunch=node,\n+                                                       data=True):\n+                    for cd in d['connect']:\n+                        if isinstance(cd[0], tuple):\n+                            args = list(cd[0])\n+                            if args[1] in functions:\n+                                funcname = functions[args[1]]\n+                            else:\n+                                func = create_function_from_source(args[1])\n+                                funcname = [name for name in func.__globals__\n+                                            if name != '__builtins__'][0]\n+                                functions[args[1]] = funcname\n+                            args[1] = funcname\n+                            args = tuple([arg for arg in args if arg])\n+                            line_args = (u.fullname.replace('.', '_'),\n+                                         args, nodename, cd[1])\n+                            line = connect_template % line_args\n+                            line = line.replace(\"'%s'\" % funcname, funcname)\n+                            lines.append(line)\n+                        else:\n+                            line_args = (u.fullname.replace('.', '_'),\n+                                         cd[0], nodename, cd[1])\n+                            lines.append(connect_template2 % line_args)\n+            functionlines = ['# Functions']\n+            for function in functions:\n+                functionlines.append(pickle.loads(function).rstrip())\n+            all_lines = importlines + functionlines + lines\n+\n+            if not filename:\n+                filename = '%s%s.py' % (prefix, self.name)\n+            with open(filename, 'wt') as fp:\n+                fp.writelines('\\n'.join(all_lines))\n+        return all_lines\n+\n+    def run(self, plugin=None, plugin_args=None, updatehash=False):\n+        \"\"\" Execute the workflow\n+\n+        Parameters\n+        ----------\n+\n+        plugin: plugin name or object\n+            Plugin to use for execution. You can create your own plugins for\n+            execution.\n+        plugin_args : dictionary containing arguments to be sent to plugin\n+            constructor. see individual plugin doc strings for details.\n+        \"\"\"\n+        if plugin is None:\n+            plugin = config.get('execution', 'plugin')\n+        if not isinstance(plugin, string_types):\n+            runner = plugin\n+        else:\n+            name = 'nipype.pipeline.plugins'\n+            try:\n+                __import__(name)\n+            except ImportError:\n+                msg = 'Could not import plugin module: %s' % name\n+                logger.error(msg)\n+                raise ImportError(msg)\n+            else:\n+                plugin_mod = getattr(sys.modules[name], '%sPlugin' % plugin)\n+                runner = plugin_mod(plugin_args=plugin_args)\n+        flatgraph = self._create_flat_graph()\n+        self.config = merge_dict(deepcopy(config._sections), self.config)\n+        if 'crashdump_dir' in self.config:\n+            warn((\"Deprecated: workflow.config['crashdump_dir']\\n\"\n+                  \"Please use config['execution']['crashdump_dir']\"))\n+            crash_dir = self.config['crashdump_dir']\n+            self.config['execution']['crashdump_dir'] = crash_dir\n+            del self.config['crashdump_dir']\n+        logger.info(str(sorted(self.config)))\n+        self._set_needed_outputs(flatgraph)\n+        execgraph = generate_expanded_graph(deepcopy(flatgraph))\n+        for index, node in enumerate(execgraph.nodes()):\n+            node.config = merge_dict(deepcopy(self.config), node.config)\n+            node.base_dir = self.base_dir\n+            node.index = index\n+            if isinstance(node, MapNode):\n+                node.use_plugin = (plugin, plugin_args)\n+        self._configure_exec_nodes(execgraph)\n+        if str2bool(self.config['execution']['create_report']):\n+            self._write_report_info(self.base_dir, self.name, execgraph)\n+        runner.run(execgraph, updatehash=updatehash, config=self.config)\n+        datestr = datetime.utcnow().strftime('%Y%m%dT%H%M%S')\n+        if str2bool(self.config['execution']['write_provenance']):\n+            prov_base = op.join(self.base_dir,\n+                                'workflow_provenance_%s' % datestr)\n+            logger.info('Provenance file prefix: %s' % prov_base)\n+            write_workflow_prov(execgraph, prov_base, format='all')\n+        return execgraph\n+\n+    # PRIVATE API AND FUNCTIONS\n+\n+    def _write_report_info(self, workingdir, name, graph):\n+        if workingdir is None:\n+            workingdir = os.getcwd()\n+        report_dir = op.join(workingdir, name)\n+        if not op.exists(report_dir):\n+            os.makedirs(report_dir)\n+        shutil.copyfile(op.join(op.dirname(__file__),\n+                                'report_template.html'),\n+                        op.join(report_dir, 'index.html'))\n+        shutil.copyfile(op.join(op.dirname(__file__),\n+                                '..', 'external', 'd3.js'),\n+                        op.join(report_dir, 'd3.js'))\n+        nodes, groups = topological_sort(graph, depth_first=True)\n+        graph_file = op.join(report_dir, 'graph1.json')\n+        json_dict = {'nodes': [], 'links': [], 'groups': [], 'maxN': 0}\n+        for i, node in enumerate(nodes):\n+            report_file = \"%s/_report/report.rst\" % \\\n+                          node.output_dir().replace(report_dir, '')\n+            result_file = \"%s/result_%s.pklz\" % \\\n+                          (node.output_dir().replace(report_dir, ''),\n+                           node.name)\n+            json_dict['nodes'].append(dict(name='%d_%s' % (i, node.name),\n+                                           report=report_file,\n+                                           result=result_file,\n+                                           group=groups[i]))\n+        maxN = 0\n+        for gid in np.unique(groups):\n+            procs = [i for i, val in enumerate(groups) if val == gid]\n+            N = len(procs)\n+            if N > maxN:\n+                maxN = N\n+            json_dict['groups'].append(dict(procs=procs,\n+                                            total=N,\n+                                            name='Group_%05d' % gid))\n+        json_dict['maxN'] = maxN\n+        for u, v in graph.in_edges_iter():\n+            json_dict['links'].append(dict(source=nodes.index(u),\n+                                           target=nodes.index(v),\n+                                           value=1))\n+        save_json(graph_file, json_dict)\n+        graph_file = op.join(report_dir, 'graph.json')\n+        template = '%%0%dd_' % np.ceil(np.log10(len(nodes))).astype(int)\n+\n+        def getname(u, i):\n+            name_parts = u.fullname.split('.')\n+            # return '.'.join(name_parts[:-1] + [template % i + name_parts[-1]])\n+            return template % i + name_parts[-1]\n+        json_dict = []\n+        for i, node in enumerate(nodes):\n+            imports = []\n+            for u, v in graph.in_edges_iter(nbunch=node):\n+                imports.append(getname(u, nodes.index(u)))\n+            json_dict.append(dict(name=getname(node, i),\n+                                  size=1,\n+                                  group=groups[i],\n+                                  imports=imports))\n+        save_json(graph_file, json_dict)\n+\n+    def _set_needed_outputs(self, graph):\n+        \"\"\"Initialize node with list of which outputs are needed.\"\"\"\n+        rm_outputs = self.config['execution']['remove_unnecessary_outputs']\n+        if not str2bool(rm_outputs):\n+            return\n+        for node in graph.nodes():\n+            node.needed_outputs = []\n+            for edge in graph.out_edges_iter(node):\n+                data = graph.get_edge_data(*edge)\n+                sourceinfo = [v1[0] if isinstance(v1, tuple) else v1\n+                              for v1, v2 in data['connect']]\n+                node.needed_outputs += [v for v in sourceinfo\n+                                        if v not in node.needed_outputs]\n+            if node.needed_outputs:\n+                node.needed_outputs = sorted(node.needed_outputs)\n+\n+    def _configure_exec_nodes(self, graph):\n+        \"\"\"Ensure that each node knows where to get inputs from\n+        \"\"\"\n+        for node in graph.nodes():\n+            node.input_source = {}\n+            for edge in graph.in_edges_iter(node):\n+                data = graph.get_edge_data(*edge)\n+                for sourceinfo, field in sorted(data['connect']):\n+                    node.input_source[field] = \\\n+                        (op.join(edge[0].output_dir(),\n+                                 'result_%s.pklz' % edge[0].name),\n+                         sourceinfo)\n+\n+    def _check_nodes(self, nodes):\n+        \"\"\"Checks if any of the nodes are already in the graph\n+\n+        \"\"\"\n+        node_names = [node.name for node in self._graph.nodes()]\n+        node_lineage = [node._hierarchy for node in self._graph.nodes()]\n+        for node in nodes:\n+            if node.name in node_names:\n+                idx = node_names.index(node.name)\n+                if node_lineage[idx] in [node._hierarchy, self.name]:\n+                    raise IOError('Duplicate node %s found.' % node)\n+            else:\n+                node_names.append(node.name)\n+\n+    def _has_attr(self, parameter, subtype='in'):\n+        \"\"\"Checks if a parameter is available as an input or output\n+        \"\"\"\n+        if subtype == 'in':\n+            subobject = self.inputs\n+        else:\n+            subobject = self.outputs\n+        attrlist = parameter.split('.')\n+        cur_out = subobject\n+        for attr in attrlist:\n+            if not hasattr(cur_out, attr):\n+                return False\n+            cur_out = getattr(cur_out, attr)\n+        return True\n+\n+    def _get_parameter_node(self, parameter, subtype='in'):\n+        \"\"\"Returns the underlying node corresponding to an input or\n+        output parameter\n+        \"\"\"\n+        if subtype == 'in':\n+            subobject = self.inputs\n+        else:\n+            subobject = self.outputs\n+        attrlist = parameter.split('.')\n+        cur_out = subobject\n+        for attr in attrlist[:-1]:\n+            cur_out = getattr(cur_out, attr)\n+        return cur_out.traits()[attrlist[-1]].node\n+\n+    def _check_outputs(self, parameter):\n+        return self._has_attr(parameter, subtype='out')\n+\n+    def _check_inputs(self, parameter):\n+        return self._has_attr(parameter, subtype='in')\n+\n+    def _get_inputs(self):\n+        \"\"\"Returns the inputs of a workflow\n+\n+        This function does not return any input ports that are already\n+        connected\n+        \"\"\"\n+        inputdict = TraitedSpec()\n+        for node in self._graph.nodes():\n+            inputdict.add_trait(node.name, traits.Instance(TraitedSpec))\n+            if isinstance(node, Workflow):\n+                setattr(inputdict, node.name, node.inputs)\n+            else:\n+                taken_inputs = []\n+                for _, _, d in self._graph.in_edges_iter(nbunch=node,\n+                                                         data=True):\n+                    for cd in d['connect']:\n+                        taken_inputs.append(cd[1])\n+                unconnectedinputs = TraitedSpec()\n+                for key, trait in list(node.inputs.items()):\n+                    if key not in taken_inputs:\n+                        unconnectedinputs.add_trait(key,\n+                                                    traits.Trait(trait,\n+                                                                 node=node))\n+                        value = getattr(node.inputs, key)\n+                        setattr(unconnectedinputs, key, value)\n+                setattr(inputdict, node.name, unconnectedinputs)\n+                getattr(inputdict, node.name).on_trait_change(self._set_input)\n+        return inputdict\n+\n+    def _get_outputs(self):\n+        \"\"\"Returns all possible output ports that are not already connected\n+        \"\"\"\n+        outputdict = TraitedSpec()\n+        for node in self._graph.nodes():\n+            outputdict.add_trait(node.name, traits.Instance(TraitedSpec))\n+            if isinstance(node, Workflow):\n+                setattr(outputdict, node.name, node.outputs)\n+            elif node.outputs:\n+                outputs = TraitedSpec()\n+                for key, _ in list(node.outputs.items()):\n+                    outputs.add_trait(key, traits.Any(node=node))\n+                    setattr(outputs, key, None)\n+                setattr(outputdict, node.name, outputs)\n+        return outputdict\n+\n+    def _set_input(self, object, name, newvalue):\n+        \"\"\"Trait callback function to update a node input\n+        \"\"\"\n+        object.traits()[name].node.set_input(name, newvalue)\n+\n+    def _set_node_input(self, node, param, source, sourceinfo):\n+        \"\"\"Set inputs of a node given the edge connection\"\"\"\n+        if isinstance(sourceinfo, string_types):\n+            val = source.get_output(sourceinfo)\n+        elif isinstance(sourceinfo, tuple):\n+            if callable(sourceinfo[1]):\n+                val = sourceinfo[1](source.get_output(sourceinfo[0]),\n+                                    *sourceinfo[2:])\n+        newval = val\n+        if isinstance(val, TraitDictObject):\n+            newval = dict(val)\n+        if isinstance(val, TraitListObject):\n+            newval = val[:]\n+        logger.debug('setting node input: %s->%s', param, str(newval))\n+        node.set_input(param, deepcopy(newval))\n+\n+    def _get_all_nodes(self):\n+        allnodes = []\n+        for node in self._graph.nodes():\n+            if isinstance(node, Workflow):\n+                allnodes.extend(node._get_all_nodes())\n+            else:\n+                allnodes.append(node)\n+        return allnodes\n+\n+    def _has_node(self, wanted_node):\n+        for node in self._graph.nodes():\n+            if wanted_node == node:\n+                return True\n+            if isinstance(node, Workflow):\n+                if node._has_node(wanted_node):\n+                    return True\n+        return False\n+\n+    def _create_flat_graph(self):\n+        \"\"\"Make a simple DAG where no node is a workflow.\"\"\"\n+        logger.debug('Creating flat graph for workflow: %s', self.name)\n+        workflowcopy = deepcopy(self)\n+        workflowcopy._generate_flatgraph()\n+        return workflowcopy._graph\n+\n+    def _reset_hierarchy(self):\n+        \"\"\"Reset the hierarchy on a graph\n+        \"\"\"\n+        for node in self._graph.nodes():\n+            if isinstance(node, Workflow):\n+                node._reset_hierarchy()\n+                for innernode in node._graph.nodes():\n+                    innernode._hierarchy = '.'.join((self.name,\n+                                                     innernode._hierarchy))\n+            else:\n+                node._hierarchy = self.name\n+\n+    def _generate_flatgraph(self):\n+        \"\"\"Generate a graph containing only Nodes or MapNodes\n+        \"\"\"\n+        logger.debug('expanding workflow: %s', self)\n+        nodes2remove = []\n+        if not nx.is_directed_acyclic_graph(self._graph):\n+            raise Exception(('Workflow: %s is not a directed acyclic graph '\n+                             '(DAG)') % self.name)\n+        nodes = nx.topological_sort(self._graph)\n+        for node in nodes:\n+            logger.debug('processing node: %s' % node)\n+            if isinstance(node, Workflow):\n+                nodes2remove.append(node)\n+                # use in_edges instead of in_edges_iter to allow\n+                # disconnections to take place properly. otherwise, the\n+                # edge dict is modified.\n+                for u, _, d in self._graph.in_edges(nbunch=node, data=True):\n+                    logger.debug('in: connections-> %s' % str(d['connect']))\n+                    for cd in deepcopy(d['connect']):\n+                        logger.debug(\"in: %s\" % str(cd))\n+                        dstnode = node._get_parameter_node(cd[1], subtype='in')\n+                        srcnode = u\n+                        srcout = cd[0]\n+                        dstin = cd[1].split('.')[-1]\n+                        logger.debug('in edges: %s %s %s %s' %\n+                                     (srcnode, srcout, dstnode, dstin))\n+                        self.disconnect(u, cd[0], node, cd[1])\n+                        self.connect(srcnode, srcout, dstnode, dstin)\n+                # do not use out_edges_iter for reasons stated in in_edges\n+                for _, v, d in self._graph.out_edges(nbunch=node, data=True):\n+                    logger.debug('out: connections-> %s' % str(d['connect']))\n+                    for cd in deepcopy(d['connect']):\n+                        logger.debug(\"out: %s\" % str(cd))\n+                        dstnode = v\n+                        if isinstance(cd[0], tuple):\n+                            parameter = cd[0][0]\n+                        else:\n+                            parameter = cd[0]\n+                        srcnode = node._get_parameter_node(parameter,\n+                                                           subtype='out')\n+                        if isinstance(cd[0], tuple):\n+                            srcout = list(cd[0])\n+                            srcout[0] = parameter.split('.')[-1]\n+                            srcout = tuple(srcout)\n+                        else:\n+                            srcout = parameter.split('.')[-1]\n+                        dstin = cd[1]\n+                        logger.debug('out edges: %s %s %s %s' % (srcnode,\n+                                                                 srcout,\n+                                                                 dstnode,\n+                                                                 dstin))\n+                        self.disconnect(node, cd[0], v, cd[1])\n+                        self.connect(srcnode, srcout, dstnode, dstin)\n+                # expand the workflow node\n+                # logger.debug('expanding workflow: %s', node)\n+                node._generate_flatgraph()\n+                for innernode in node._graph.nodes():\n+                    innernode._hierarchy = '.'.join((self.name,\n+                                                     innernode._hierarchy))\n+                self._graph.add_nodes_from(node._graph.nodes())\n+                self._graph.add_edges_from(node._graph.edges(data=True))\n+        if nodes2remove:\n+            self._graph.remove_nodes_from(nodes2remove)\n+        logger.debug('finished expanding workflow: %s', self)\n+\n+    def _get_dot(self, prefix=None, hierarchy=None, colored=False,\n+                 simple_form=True, level=0):\n+        \"\"\"Create a dot file with connection info\n+        \"\"\"\n+        if prefix is None:\n+            prefix = '  '\n+        if hierarchy is None:\n+            hierarchy = []\n+        colorset = ['#FFFFC8', '#0000FF', '#B4B4FF', '#E6E6FF', '#FF0000',\n+                    '#FFB4B4', '#FFE6E6', '#00A300', '#B4FFB4', '#E6FFE6']\n+\n+        dotlist = ['%slabel=\"%s\";' % (prefix, self.name)]\n+        for node in nx.topological_sort(self._graph):\n+            fullname = '.'.join(hierarchy + [node.fullname])\n+            nodename = fullname.replace('.', '_')\n+            if not isinstance(node, Workflow):\n+                node_class_name = get_print_name(node, simple_form=simple_form)\n+                if not simple_form:\n+                    node_class_name = '.'.join(node_class_name.split('.')[1:])\n+                if hasattr(node, 'iterables') and node.iterables:\n+                    dotlist.append(('%s[label=\"%s\", shape=box3d,'\n+                                    'style=filled, color=black, colorscheme'\n+                                    '=greys7 fillcolor=2];') % (nodename,\n+                                                                node_class_name))\n+                else:\n+                    if colored:\n+                        dotlist.append(('%s[label=\"%s\", style=filled,'\n+                                        ' fillcolor=\"%s\"];')\n+                                       % (nodename, node_class_name,\n+                                           colorset[level]))\n+                    else:\n+                        dotlist.append(('%s[label=\"%s\"];')\n+                                       % (nodename, node_class_name))\n+\n+        for node in nx.topological_sort(self._graph):\n+            if isinstance(node, Workflow):\n+                fullname = '.'.join(hierarchy + [node.fullname])\n+                nodename = fullname.replace('.', '_')\n+                dotlist.append('subgraph cluster_%s {' % nodename)\n+                if colored:\n+                    dotlist.append(prefix + prefix + 'edge [color=\"%s\"];' % (colorset[level + 1]))\n+                    dotlist.append(prefix + prefix + 'style=filled;')\n+                    dotlist.append(prefix + prefix + 'fillcolor=\"%s\";' % (colorset[level + 2]))\n+                dotlist.append(node._get_dot(prefix=prefix + prefix,\n+                                             hierarchy=hierarchy + [self.name],\n+                                             colored=colored,\n+                                             simple_form=simple_form, level=level + 3))\n+                dotlist.append('}')\n+                if level == 6:\n+                    level = 2\n+            else:\n+                for subnode in self._graph.successors_iter(node):\n+                    if node._hierarchy != subnode._hierarchy:\n+                        continue\n+                    if not isinstance(subnode, Workflow):\n+                        nodefullname = '.'.join(hierarchy + [node.fullname])\n+                        subnodefullname = '.'.join(hierarchy +\n+                                                   [subnode.fullname])\n+                        nodename = nodefullname.replace('.', '_')\n+                        subnodename = subnodefullname.replace('.', '_')\n+                        for _ in self._graph.get_edge_data(node,\n+                                                           subnode)['connect']:\n+                            dotlist.append('%s -> %s;' % (nodename,\n+                                                          subnodename))\n+                        logger.debug('connection: ' + dotlist[-1])\n+        # add between workflow connections\n+        for u, v, d in self._graph.edges_iter(data=True):\n+            uname = '.'.join(hierarchy + [u.fullname])\n+            vname = '.'.join(hierarchy + [v.fullname])\n+            for src, dest in d['connect']:\n+                uname1 = uname\n+                vname1 = vname\n+                if isinstance(src, tuple):\n+                    srcname = src[0]\n+                else:\n+                    srcname = src\n+                if '.' in srcname:\n+                    uname1 += '.' + '.'.join(srcname.split('.')[:-1])\n+                if '.' in dest and '@' not in dest:\n+                    if not isinstance(v, Workflow):\n+                        if 'datasink' not in \\\n+                           str(v._interface.__class__).lower():\n+                            vname1 += '.' + '.'.join(dest.split('.')[:-1])\n+                    else:\n+                        vname1 += '.' + '.'.join(dest.split('.')[:-1])\n+                if uname1.split('.')[:-1] != vname1.split('.')[:-1]:\n+                    dotlist.append('%s -> %s;' % (uname1.replace('.', '_'),\n+                                                  vname1.replace('.', '_')))\n+                    logger.debug('cross connection: ' + dotlist[-1])\n+        return ('\\n' + prefix).join(dotlist)\n+\n+    def _plain_connect(self, *args, **kwargs):\n+        super(ConditionalWorkflow, self).connect(*args, **kwargs)\n+\n+\n+class ConditionalWorkflow(Workflow):\n+    \"\"\"\n+    Implements a kind of workflow that can be by-passed if the input of\n+    `donotrun` of the condition node is `True`.\n+    \"\"\"\n+\n+    def __init__(self, name, base_dir=None):\n+        \"\"\"Create a workflow object.\n+        Parameters\n+        ----------\n+        name : alphanumeric string\n+            unique identifier for the workflow\n+        base_dir : string, optional\n+            path to workflow storage\n+        \"\"\"\n+\n+        from nipype.interfaces.utility import IdentityInterface\n+        super(ConditionalWorkflow, self).__init__(name, base_dir)\n+        self._condition = Node(IdentityInterface(fields=['donotrun']),\n+                               name='checknode')\n+        self.add_nodes([self._condition])\n+\n+    def _check_conditional_nodes(self):\n+        from nipype.interfaces.utility import IdentityInterface\n+\n+        def _checkdefined(val):\n+            from nipype.interfaces.base import isdefined\n+            if isdefined(val):\n+                return bool(val)\n+            return False\n+\n+        if checknode is None:","path":"nipype/pipeline/engine/workflows.py","position":977,"original_position":977,"commit_id":"57684f29552062fba43adcfacd49998f539aab03","original_commit_id":"57684f29552062fba43adcfacd49998f539aab03","user":{"login":"chrisfilo","id":238759,"avatar_url":"https://avatars.githubusercontent.com/u/238759?v=3","gravatar_id":"","url":"https://api.github.com/users/chrisfilo","html_url":"https://github.com/chrisfilo","followers_url":"https://api.github.com/users/chrisfilo/followers","following_url":"https://api.github.com/users/chrisfilo/following{/other_user}","gists_url":"https://api.github.com/users/chrisfilo/gists{/gist_id}","starred_url":"https://api.github.com/users/chrisfilo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chrisfilo/subscriptions","organizations_url":"https://api.github.com/users/chrisfilo/orgs","repos_url":"https://api.github.com/users/chrisfilo/repos","events_url":"https://api.github.com/users/chrisfilo/events{/privacy}","received_events_url":"https://api.github.com/users/chrisfilo/received_events","type":"User","site_admin":false},"body":"![Codacy](https://www.codacy.com/versioned/images/favicon.png) Issue found: [Using variable 'checknode' before assignment](https://www.codacy.com/app/krzysztof-gorgolewski/nipy_nipype/file/1590241444/issues/source?bid=2767207&fileBranchId=3022732#l977)","created_at":"2015-12-16T22:24:52Z","updated_at":"2015-12-16T22:24:52Z","html_url":"https://github.com/nipy/nipype/pull/1299#discussion_r47843242","pull_request_url":"https://api.github.com/repos/nipy/nipype/pulls/1299","_links":{"self":{"href":"https://api.github.com/repos/nipy/nipype/pulls/comments/47843242"},"html":{"href":"https://github.com/nipy/nipype/pull/1299#discussion_r47843242"},"pull_request":{"href":"https://api.github.com/repos/nipy/nipype/pulls/1299"}}},"pull_request":{"url":"https://api.github.com/repos/nipy/nipype/pulls/1299","id":53688655,"html_url":"https://github.com/nipy/nipype/pull/1299","diff_url":"https://github.com/nipy/nipype/pull/1299.diff","patch_url":"https://github.com/nipy/nipype/pull/1299.patch","issue_url":"https://api.github.com/repos/nipy/nipype/issues/1299","number":1299,"state":"open","locked":false,"title":"[WIP,ENH] Conditional Nodes and Workflows","user":{"login":"oesteban","id":598470,"avatar_url":"https://avatars.githubusercontent.com/u/598470?v=3","gravatar_id":"","url":"https://api.github.com/users/oesteban","html_url":"https://github.com/oesteban","followers_url":"https://api.github.com/users/oesteban/followers","following_url":"https://api.github.com/users/oesteban/following{/other_user}","gists_url":"https://api.github.com/users/oesteban/gists{/gist_id}","starred_url":"https://api.github.com/users/oesteban/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/oesteban/subscriptions","organizations_url":"https://api.github.com/users/oesteban/orgs","repos_url":"https://api.github.com/users/oesteban/repos","events_url":"https://api.github.com/users/oesteban/events{/privacy}","received_events_url":"https://api.github.com/users/oesteban/received_events","type":"User","site_admin":false},"body":"This is a first take on conditional execution of nodes in workflows. Runtime decisions in nipype are old feature requests (i.e. #819 and #878 for nodes, and #1081 for workflows).\r\n\r\nThe new ```ConditionalNode``` adds an special input to the inner interface named ```donotrun``` that can be used to disable the node. It is the user's responsibility to maintain the consistency along the whole graph.\r\n\r\nThe new ```ConditionalWorkflow``` adds an special input node: when setting ```checknode.donotrun``` to ```True```, the workflow is disabled. These workflows automatically cast all its nodes to ```ConditionalNode```s and connected to the ```checknode.donotrun``` input.\r\n\r\nFinally, a new ```CachedWorkflow``` is derived from ```ConditionalWorkflow```, with the addition of a ```cache_map``` (list of tuples indicating the input name and the output to which it is mapped). If all the inputs of a ```cachenode``` (as listed in the first element of the ```cache_map``` list of tuples) are defined, the ```donotrun``` of the workflow is activated and all the nodes skipped. Additionally, the workflows outputs are mapped from the ```cachenode```. Otherwise, they are filled with the normal execution of the workflow.\r\n\r\nExample:\r\n\r\n```\r\nimport os\r\nfrom nipype.pipeline import engine as pe\r\nfrom nipype.interfaces import utility as niu\r\n\r\ndef _sum(a, b):\r\n    return a + b\r\n\r\ncwf = pe.CachedWorkflow(\r\n    'TestCachedWorkflow', cache_map=[('c', 'out1'), ('d', 'out2')])\r\ncwf.base_dir = os.getcwd()\r\n\r\ninputnode = pe.Node(niu.IdentityInterface(fields=['a', 'b']),\r\n                    name='inputnode')\r\nsumnode = pe.Node(niu.Function(\r\n    input_names=['a', 'b'], output_names=['sum'],\r\n    function=_sum), name='SumNode')\r\ncwf.connect([\r\n    (inputnode, sumnode, [('a', 'a'), ('b', 'b')]),\r\n    (sumnode, 'output', [('sum', 'out1'), ('sum', 'out2')])\r\n])\r\n\r\ncwf.inputs.inputnode.a = 2\r\ncwf.inputs.inputnode.b = 3\r\n# cwf.inputs.cachenode.c = 1\r\ncwf.inputs.cachenode.d = 7\r\n\r\ncwf.run()\r\n\r\n```\r\nThis will return ```outputnode.out1 = 5``` and ```outputnode.out2 = 5```\r\nIf the line ```# cwf.inputs.cachenode.c = 1``` is uncommented, then ```outputnode.out1 = 1``` and ```outputnode.out2 = 7``` since all the cache inputs are set and mapped.\r\n\r\nThis PR is labeled as WIP because there are some missing elements:\r\n  - [ ] Include this example in the documentation, improve documentation\r\n  - [ ] Analyze side-effects of skipping nodes, currently this is just a hard decision. Presumably, something should be cached when ```donotrun``` is ```True```.\r\n  - [ ] Convert sub-workflows of ConditionalWorkflow and CachedWorkflow in ConditionalWorkflows and connect the ```donotrun``` input.\r\n\r\nThis PR requires #1298.","created_at":"2015-12-15T11:04:19Z","updated_at":"2015-12-16T22:24:53Z","closed_at":null,"merged_at":null,"merge_commit_sha":"778fc9c5ce0ce7bc29026877fa6a2d6c2c9958f6","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/nipy/nipype/pulls/1299/commits","review_comments_url":"https://api.github.com/repos/nipy/nipype/pulls/1299/comments","review_comment_url":"https://api.github.com/repos/nipy/nipype/pulls/comments{/number}","comments_url":"https://api.github.com/repos/nipy/nipype/issues/1299/comments","statuses_url":"https://api.github.com/repos/nipy/nipype/statuses/57684f29552062fba43adcfacd49998f539aab03","head":{"label":"oesteban:enh/ControlNodes","ref":"enh/ControlNodes","sha":"57684f29552062fba43adcfacd49998f539aab03","user":{"login":"oesteban","id":598470,"avatar_url":"https://avatars.githubusercontent.com/u/598470?v=3","gravatar_id":"","url":"https://api.github.com/users/oesteban","html_url":"https://github.com/oesteban","followers_url":"https://api.github.com/users/oesteban/followers","following_url":"https://api.github.com/users/oesteban/following{/other_user}","gists_url":"https://api.github.com/users/oesteban/gists{/gist_id}","starred_url":"https://api.github.com/users/oesteban/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/oesteban/subscriptions","organizations_url":"https://api.github.com/users/oesteban/orgs","repos_url":"https://api.github.com/users/oesteban/repos","events_url":"https://api.github.com/users/oesteban/events{/privacy}","received_events_url":"https://api.github.com/users/oesteban/received_events","type":"User","site_admin":false},"repo":{"id":17021985,"name":"nipype","full_name":"oesteban/nipype","owner":{"login":"oesteban","id":598470,"avatar_url":"https://avatars.githubusercontent.com/u/598470?v=3","gravatar_id":"","url":"https://api.github.com/users/oesteban","html_url":"https://github.com/oesteban","followers_url":"https://api.github.com/users/oesteban/followers","following_url":"https://api.github.com/users/oesteban/following{/other_user}","gists_url":"https://api.github.com/users/oesteban/gists{/gist_id}","starred_url":"https://api.github.com/users/oesteban/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/oesteban/subscriptions","organizations_url":"https://api.github.com/users/oesteban/orgs","repos_url":"https://api.github.com/users/oesteban/repos","events_url":"https://api.github.com/users/oesteban/events{/privacy}","received_events_url":"https://api.github.com/users/oesteban/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/oesteban/nipype","description":"Workflows and interfaces for neuroimaging packages","fork":true,"url":"https://api.github.com/repos/oesteban/nipype","forks_url":"https://api.github.com/repos/oesteban/nipype/forks","keys_url":"https://api.github.com/repos/oesteban/nipype/keys{/key_id}","collaborators_url":"https://api.github.com/repos/oesteban/nipype/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/oesteban/nipype/teams","hooks_url":"https://api.github.com/repos/oesteban/nipype/hooks","issue_events_url":"https://api.github.com/repos/oesteban/nipype/issues/events{/number}","events_url":"https://api.github.com/repos/oesteban/nipype/events","assignees_url":"https://api.github.com/repos/oesteban/nipype/assignees{/user}","branches_url":"https://api.github.com/repos/oesteban/nipype/branches{/branch}","tags_url":"https://api.github.com/repos/oesteban/nipype/tags","blobs_url":"https://api.github.com/repos/oesteban/nipype/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/oesteban/nipype/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/oesteban/nipype/git/refs{/sha}","trees_url":"https://api.github.com/repos/oesteban/nipype/git/trees{/sha}","statuses_url":"https://api.github.com/repos/oesteban/nipype/statuses/{sha}","languages_url":"https://api.github.com/repos/oesteban/nipype/languages","stargazers_url":"https://api.github.com/repos/oesteban/nipype/stargazers","contributors_url":"https://api.github.com/repos/oesteban/nipype/contributors","subscribers_url":"https://api.github.com/repos/oesteban/nipype/subscribers","subscription_url":"https://api.github.com/repos/oesteban/nipype/subscription","commits_url":"https://api.github.com/repos/oesteban/nipype/commits{/sha}","git_commits_url":"https://api.github.com/repos/oesteban/nipype/git/commits{/sha}","comments_url":"https://api.github.com/repos/oesteban/nipype/comments{/number}","issue_comment_url":"https://api.github.com/repos/oesteban/nipype/issues/comments{/number}","contents_url":"https://api.github.com/repos/oesteban/nipype/contents/{+path}","compare_url":"https://api.github.com/repos/oesteban/nipype/compare/{base}...{head}","merges_url":"https://api.github.com/repos/oesteban/nipype/merges","archive_url":"https://api.github.com/repos/oesteban/nipype/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/oesteban/nipype/downloads","issues_url":"https://api.github.com/repos/oesteban/nipype/issues{/number}","pulls_url":"https://api.github.com/repos/oesteban/nipype/pulls{/number}","milestones_url":"https://api.github.com/repos/oesteban/nipype/milestones{/number}","notifications_url":"https://api.github.com/repos/oesteban/nipype/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/oesteban/nipype/labels{/name}","releases_url":"https://api.github.com/repos/oesteban/nipype/releases{/id}","created_at":"2014-02-20T13:58:05Z","updated_at":"2015-04-26T18:20:51Z","pushed_at":"2015-12-16T19:49:05Z","git_url":"git://github.com/oesteban/nipype.git","ssh_url":"git@github.com:oesteban/nipype.git","clone_url":"https://github.com/oesteban/nipype.git","svn_url":"https://github.com/oesteban/nipype","homepage":"http://www.mit.edu/~satra/nipype-nightly/","size":24403,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"nipy:master","ref":"master","sha":"135362809a5592abc3e0aa11b44859e414b3c988","user":{"login":"nipy","id":233707,"avatar_url":"https://avatars.githubusercontent.com/u/233707?v=3","gravatar_id":"","url":"https://api.github.com/users/nipy","html_url":"https://github.com/nipy","followers_url":"https://api.github.com/users/nipy/followers","following_url":"https://api.github.com/users/nipy/following{/other_user}","gists_url":"https://api.github.com/users/nipy/gists{/gist_id}","starred_url":"https://api.github.com/users/nipy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nipy/subscriptions","organizations_url":"https://api.github.com/users/nipy/orgs","repos_url":"https://api.github.com/users/nipy/repos","events_url":"https://api.github.com/users/nipy/events{/privacy}","received_events_url":"https://api.github.com/users/nipy/received_events","type":"Organization","site_admin":false},"repo":{"id":791477,"name":"nipype","full_name":"nipy/nipype","owner":{"login":"nipy","id":233707,"avatar_url":"https://avatars.githubusercontent.com/u/233707?v=3","gravatar_id":"","url":"https://api.github.com/users/nipy","html_url":"https://github.com/nipy","followers_url":"https://api.github.com/users/nipy/followers","following_url":"https://api.github.com/users/nipy/following{/other_user}","gists_url":"https://api.github.com/users/nipy/gists{/gist_id}","starred_url":"https://api.github.com/users/nipy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nipy/subscriptions","organizations_url":"https://api.github.com/users/nipy/orgs","repos_url":"https://api.github.com/users/nipy/repos","events_url":"https://api.github.com/users/nipy/events{/privacy}","received_events_url":"https://api.github.com/users/nipy/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/nipy/nipype","description":"Workflows and interfaces for neuroimaging packages","fork":false,"url":"https://api.github.com/repos/nipy/nipype","forks_url":"https://api.github.com/repos/nipy/nipype/forks","keys_url":"https://api.github.com/repos/nipy/nipype/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nipy/nipype/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nipy/nipype/teams","hooks_url":"https://api.github.com/repos/nipy/nipype/hooks","issue_events_url":"https://api.github.com/repos/nipy/nipype/issues/events{/number}","events_url":"https://api.github.com/repos/nipy/nipype/events","assignees_url":"https://api.github.com/repos/nipy/nipype/assignees{/user}","branches_url":"https://api.github.com/repos/nipy/nipype/branches{/branch}","tags_url":"https://api.github.com/repos/nipy/nipype/tags","blobs_url":"https://api.github.com/repos/nipy/nipype/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nipy/nipype/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nipy/nipype/git/refs{/sha}","trees_url":"https://api.github.com/repos/nipy/nipype/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nipy/nipype/statuses/{sha}","languages_url":"https://api.github.com/repos/nipy/nipype/languages","stargazers_url":"https://api.github.com/repos/nipy/nipype/stargazers","contributors_url":"https://api.github.com/repos/nipy/nipype/contributors","subscribers_url":"https://api.github.com/repos/nipy/nipype/subscribers","subscription_url":"https://api.github.com/repos/nipy/nipype/subscription","commits_url":"https://api.github.com/repos/nipy/nipype/commits{/sha}","git_commits_url":"https://api.github.com/repos/nipy/nipype/git/commits{/sha}","comments_url":"https://api.github.com/repos/nipy/nipype/comments{/number}","issue_comment_url":"https://api.github.com/repos/nipy/nipype/issues/comments{/number}","contents_url":"https://api.github.com/repos/nipy/nipype/contents/{+path}","compare_url":"https://api.github.com/repos/nipy/nipype/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nipy/nipype/merges","archive_url":"https://api.github.com/repos/nipy/nipype/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nipy/nipype/downloads","issues_url":"https://api.github.com/repos/nipy/nipype/issues{/number}","pulls_url":"https://api.github.com/repos/nipy/nipype/pulls{/number}","milestones_url":"https://api.github.com/repos/nipy/nipype/milestones{/number}","notifications_url":"https://api.github.com/repos/nipy/nipype/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nipy/nipype/labels{/name}","releases_url":"https://api.github.com/repos/nipy/nipype/releases{/id}","created_at":"2010-07-22T17:06:49Z","updated_at":"2015-12-14T11:32:18Z","pushed_at":"2015-12-16T19:49:06Z","git_url":"git://github.com/nipy/nipype.git","ssh_url":"git@github.com:nipy/nipype.git","clone_url":"https://github.com/nipy/nipype.git","svn_url":"https://github.com/nipy/nipype","homepage":"http://www.mit.edu/~satra/nipype-nightly/","size":64627,"stargazers_count":155,"watchers_count":155,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":189,"mirror_url":null,"open_issues_count":202,"forks":189,"open_issues":202,"watchers":155,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/nipy/nipype/pulls/1299"},"html":{"href":"https://github.com/nipy/nipype/pull/1299"},"issue":{"href":"https://api.github.com/repos/nipy/nipype/issues/1299"},"comments":{"href":"https://api.github.com/repos/nipy/nipype/issues/1299/comments"},"review_comments":{"href":"https://api.github.com/repos/nipy/nipype/pulls/1299/comments"},"review_comment":{"href":"https://api.github.com/repos/nipy/nipype/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/nipy/nipype/pulls/1299/commits"},"statuses":{"href":"https://api.github.com/repos/nipy/nipype/statuses/57684f29552062fba43adcfacd49998f539aab03"}}}}