Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions nipype/interfaces/workbench/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:

from .metric import MetricResample
69 changes: 69 additions & 0 deletions nipype/interfaces/workbench/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
"""
The workbench module provides classes for interfacing with `connectome workbench
<https://www.humanconnectome.org/software/connectome-workbench>`_ tools.

`Connectome Workbench is an open source, freely available visualization and
discovery tool used to map neuroimaging data, especially data generated by the
Human Connectome Project.
"""

from __future__ import (print_function, division, unicode_literals,
absolute_import)
import os
import re

from ... import logging
from ...utils.filemanip import split_filename
from ..base import CommandLine, PackageInfo

iflogger = logging.getLogger('interface')


class Info(PackageInfo):
"""
Handle `wb_command` version information.
"""

version_cmd = 'wb_command -version'

@staticmethod
def parse_version(raw_info):
m = re.search(r'\nVersion (\S+)', raw_info)
return m.groups()[0] if m else None


class WBCommand(CommandLine):
"""Base support for workbench commands."""

@property
def version(self):
return Info.version()

def _gen_filename(self, name, outdir=None, suffix='', ext=None):
"""Generate a filename based on the given parameters.
The filename will take the form: <basename><suffix><ext>.
Parameters
----------
name : str
Filename to base the new filename on.
suffix : str
Suffix to add to the `basename`. (defaults is '' )
ext : str
Extension to use for the new filename.
Returns
-------
fname : str
New filename based on given parameters.
"""
if not name:
raise ValueError("Cannot generate filename - filename not set")

_, fname, fext = split_filename(name)
if ext is None:
ext = fext
if outdir is None:
outdir = '.'
return os.path.join(outdir, fname + suffix + ext)
147 changes: 147 additions & 0 deletions nipype/interfaces/workbench/metric.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# -*- coding: utf-8 -*-
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
"""This module provides interfaces for workbench surface commands"""
from __future__ import (print_function, division, unicode_literals,
absolute_import)
import os

from ..base import (TraitedSpec, File, traits, InputMultiObject,
CommandLineInputSpec)
from .base import WBCommand
from ... import logging

iflogger = logging.getLogger('interface')

class MetricResampleInputSpec(CommandLineInputSpec):
in_file = File(
exists=True,
mandatory=True,
argstr="%s",
position=0,
desc="The metric file to resample")
current_sphere = File(
exists=True,
mandatory=True,
argstr="%s",
position=1,
desc="A sphere surface with the mesh that the metric is currently on")
new_sphere = File(
exists=True,
mandatory=True,
argstr="%s",
position=2,
desc="A sphere surface that is in register with <current-sphere> and"
" has the desired output mesh")
method = traits.Enum(
"ADAP_BARY_AREA",
"BARYCENTRIC",
argstr="%s",
mandatory=True,
position=3,
desc="The method name - ADAP_BARY_AREA method is recommended for"
" ordinary metric data, because it should use all data while"
" downsampling, unlike BARYCENTRIC. If ADAP_BARY_AREA is used,"
" exactly one of area_surfs or area_metrics must be specified")
out_file = File(
name_source=["new_sphere"],
name_template="%s.out",
keep_extension=True,
argstr="%s",
position=4,
desc="The output metric")
Copy link
Member

Choose a reason for hiding this comment

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

Maybe rename this to out_file (or rename both here and outputs.out_file to metric_file)? In addition to resolving your trait weirdness, I think it'll be less confusing for people for the connected traits to have the same name.

area_surfs = traits.Bool(
position=5,
argstr="-area-surfs",
xor=["area_metrics"],
desc="Specify surfaces to do vertex area correction based on")
area_metrics = traits.Bool(
position=5,
argstr="-area-metrics",
xor=["area_surfs"],
desc="Specify vertex area metrics to do area correction based on")
current_area = File(
exists=True,
position=6,
argstr="%s",
desc="A relevant anatomical surface with <current-sphere> mesh OR"
" a metric file with vertex areas for <current-sphere> mesh")
new_area = File(
exists=True,
position=7,
argstr="%s",
desc="A relevant anatomical surface with <current-sphere> mesh OR"
" a metric file with vertex areas for <current-sphere> mesh")
roi_metric = File(
exists=True,
position=8,
argstr="-current-roi %s",
desc="Input roi on the current mesh used to exclude non-data vertices")
valid_roi_out = traits.Bool(
position=9,
argstr="-valid-roi-out",
desc="Output the ROI of vertices that got data from valid source vertices")
largest = traits.Bool(
position=10,
argstr="-largest",
desc="Use only the value of the vertex with the largest weight")


class MetricResampleOutputSpec(TraitedSpec):
out_file = File(exists=True, desc="the output metric")
roi_file = File(desc="ROI of vertices that got data from valid source vertices")


class MetricResample(WBCommand):
"""
Resample a metric file to a different mesh

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would help documentation to copy a bit more from their docs:

      Resample a metric file to a different mesh

      Resamples a metric file, given two spherical surfaces that are in
      register.  If ``ADAP_BARY_AREA`` is used, exactly one of -area-surfs or
      ``-area-metrics`` must be specified.

      The ``ADAP_BARY_AREA`` method is recommended for ordinary metric data,
      because it should use all data while downsampling, unlike ``BARYCENTRIC``.
      The recommended areas option for most data is individual midthicknesses
      for individual data, and averaged vertex area metrics from individual
      midthicknesses for group average data.

      The ``-current-roi`` option only masks the input, the output may be slightly
      dilated in comparison, consider using ``-metric-mask`` on the output when
      using ``-current-roi``.

      The ``-largest option`` results in nearest vertex behavior when used with
      ``BARYCENTRIC``.  When resampling a binary metric, consider thresholding at
      0.5 after resampling rather than using ``-largest``.

Copy link
Member

Choose a reason for hiding this comment

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

@oesteban Want to just push this change? It's after 5pm here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure

>>> from nipype.interfaces.workbench import MetricResample
>>> metres = MetricResample()
>>> metres.inputs.in_file = 'sub-01_task-rest_bold_space-fsaverage5.L.func.gii'
>>> metres.inputs.method = 'ADAP_BARY_AREA'
>>> metres.inputs.current_sphere = 'fsaverage5_std_sphere.L.10k_fsavg_L.surf.gii'
>>> metres.inputs.new_sphere = 'fs_LR-deformed_to-fsaverage.L.sphere.32k_fs_LR.surf.gii'
>>> metres.inputs.area_metrics = True
>>> metres.inputs.current_area = 'fsaverage5.L.midthickness_va_avg.10k_fsavg_L.shape.gii'
>>> metres.inputs.new_area = 'fs_LR.L.midthickness_va_avg.32k_fs_LR.shape.gii'
>>> metres.cmdline
'wb_command -metric-resample sub-01_task-rest_bold_space-fsaverage5.L.func.gii \
fsaverage5_std_sphere.L.10k_fsavg_L.surf.gii \
fs_LR-deformed_to-fsaverage.L.sphere.32k_fs_LR.surf.gii \
ADAP_BARY_AREA fs_LR-deformed_to-fsaverage.L.sphere.32k_fs_LR.surf.out \
-area-metrics fsaverage5.L.midthickness_va_avg.10k_fsavg_L.shape.gii \
fs_LR.L.midthickness_va_avg.32k_fs_LR.shape.gii'
"""
input_spec = MetricResampleInputSpec
output_spec = MetricResampleOutputSpec
_cmd = 'wb_command -metric-resample'

def _format_arg(self, opt, spec, val):
if opt == "out_file":
# ensure generated filename is assigned to trait
self.inputs.trait_set(out_file=val)
Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I meant to say that I think this (and the _list_outputs entry) shouldn't be necessary if you make the input spec match the output spec.

if opt in ['current_area', 'new_area']:
if not self.inputs.area_surfs and not self.inputs.area_metrics:
raise ValueError("{} was set but neither area_surfs or"
" area_metrics were set".format(opt))
if opt == "method":
if (val == "ADAP_BARY_AREA" and
not self.inputs.area_surfs and
not self.inputs.area_metrics):
raise ValueError("Exactly one of area_surfs or area_metrics"
" must be specified")
if opt == "valid_roi_out" and val:
# generate a filename and add it to argstr
roi_out = self._gen_filename(self.inputs.in_file, suffix='_roi')
iflogger.info("Setting roi output file as", roi_out)
spec.argstr += " " + roi_out
return super(MetricResample, self)._format_arg(opt, spec, val)

def _list_outputs(self):
outputs = self._outputs().get()
outputs['out_file'] = os.path.abspath(self.inputs.out_file)
if self.inputs.valid_roi_out:
roi_file = self._gen_filename(self.inputs.in_file, suffix='_roi')
outputs['roi_file'] = os.path.abspath(roi)
return outputs
Empty file.
94 changes: 94 additions & 0 deletions nipype/interfaces/workbench/tests/test_auto_MetricResample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT
from __future__ import unicode_literals
from ..metric import MetricResample


def test_MetricResample_inputs():
input_map = dict(
area_metrics=dict(
argstr='-area-metrics',
position=5,
xor=['area_surfs'],
),
area_surfs=dict(
argstr='-area-surfs',
position=5,
xor=['area_metrics'],
),
args=dict(argstr='%s', ),
current_area=dict(
argstr='%s',
position=6,
),
current_sphere=dict(
argstr='%s',
mandatory=True,
position=1,
),
environ=dict(
nohash=True,
usedefault=True,
),
ignore_exception=dict(
deprecated='1.0.0',
nohash=True,
usedefault=True,
),
in_file=dict(
argstr='%s',
mandatory=True,
position=0,
),
largest=dict(
argstr='-largest',
position=10,
),
method=dict(
argstr='%s',
mandatory=True,
position=3,
),
new_area=dict(
argstr='%s',
position=7,
),
new_sphere=dict(
argstr='%s',
mandatory=True,
position=2,
),
out_file=dict(
argstr='%s',
keep_extension=True,
name_source=['new_sphere'],
name_template='%s.out',
position=4,
),
roi_metric=dict(
argstr='-current-roi %s',
position=8,
),
terminal_output=dict(
deprecated='1.0.0',
nohash=True,
),
valid_roi_out=dict(
argstr='-valid-roi-out',
position=9,
),
)
inputs = MetricResample.input_spec()

for key, metadata in list(input_map.items()):
for metakey, value in list(metadata.items()):
assert getattr(inputs.traits()[key], metakey) == value
def test_MetricResample_outputs():
output_map = dict(
out_file=dict(),
roi_file=dict(),
)
outputs = MetricResample.output_spec()

for key, metadata in list(output_map.items()):
for metakey, value in list(metadata.items()):
assert getattr(outputs.traits()[key], metakey) == value
27 changes: 27 additions & 0 deletions nipype/interfaces/workbench/tests/test_auto_WBCommand.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT
from __future__ import unicode_literals
from ..base import WBCommand


def test_WBCommand_inputs():
input_map = dict(
args=dict(argstr='%s', ),
environ=dict(
nohash=True,
usedefault=True,
),
ignore_exception=dict(
deprecated='1.0.0',
nohash=True,
usedefault=True,
),
terminal_output=dict(
deprecated='1.0.0',
nohash=True,
),
)
inputs = WBCommand.input_spec()

for key, metadata in list(input_map.items()):
for metakey, value in list(metadata.items()):
assert getattr(inputs.traits()[key], metakey) == value
Empty file.
Empty file.
Empty file.