Skip to content

Commit 61978ad

Browse files
authored
Merge pull request #1790 from effigies/freesurfer6
[ENH] Update ReconAll interface for v6
2 parents 4dc5ab1 + 06a9eb9 commit 61978ad

File tree

4 files changed

+245
-98
lines changed

4 files changed

+245
-98
lines changed

CHANGES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Upcoming release 0.13
2929
* ENH: Added support for custom job submission check in SLURM (https://github.com/nipy/nipype/pull/1582)
3030
* ENH: Added ANTs interface CreateJacobianDeterminantImage; replaces deprecated JacobianDeterminant
3131
(https://github.com/nipy/nipype/pull/1654)
32+
* ENH: Update ReconAll interface for FreeSurfer v6.0.0 (https://github.com/nipy/nipype/pull/1790)
3233

3334
Release 0.12.1 (August 3, 2016)
3435
===============================

nipype/interfaces/freesurfer/preprocess.py

Lines changed: 192 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,29 @@
2121
import numpy as np
2222
from nibabel import load
2323

24-
from ... import logging
25-
from ...utils.filemanip import fname_presuffix
24+
from ... import logging, LooseVersion
25+
from ...utils.filemanip import fname_presuffix, check_depends
2626
from ..io import FreeSurferSource
2727
from ..base import (TraitedSpec, File, traits,
2828
Directory, InputMultiPath,
2929
OutputMultiPath, CommandLine,
3030
CommandLineInputSpec, isdefined)
3131
from .base import (FSCommand, FSTraitedSpec,
3232
FSTraitedSpecOpenMP,
33-
FSCommandOpenMP)
33+
FSCommandOpenMP, Info)
3434
from .utils import copy2subjdir
3535

3636
__docformat__ = 'restructuredtext'
3737
iflogger = logging.getLogger('interface')
3838

39+
FSVersion = "0"
40+
_ver = Info.version()
41+
if _ver:
42+
if 'dev' in _ver:
43+
FSVersion = _ver.rstrip().split('-')[-1] + '.dev'
44+
else:
45+
FSVersion = _ver.rstrip().split('-v')[-1]
46+
3947

4048
class ParseDICOMDirInputSpec(FSTraitedSpec):
4149
dicom_dir = Directory(exists=True, argstr='--d %s', mandatory=True,
@@ -622,6 +630,8 @@ class ReconAllInputSpec(CommandLineInputSpec):
622630
desc='Use converted T2 to refine the cortical surface')
623631
openmp = traits.Int(argstr="-openmp %d",
624632
desc="Number of processors to use in parallel")
633+
parallel = traits.Bool(argstr="-parallel",
634+
desc="Enable parallel execution")
625635
subjects_dir = Directory(exists=True, argstr='-sd %s', hash_files=False,
626636
desc='path to subjects directory', genfile=True)
627637
flags = traits.Str(argstr='%s', desc='additional parameters')
@@ -656,85 +666,173 @@ class ReconAll(CommandLine):
656666
output_spec = ReconAllOutputSpec
657667
_can_resume = True
658668

659-
_steps = [
660-
# autorecon1
661-
('motioncor', ['mri/rawavg.mgz', 'mri/orig.mgz']),
662-
('talairach', ['mri/transforms/talairach.auto.xfm',
663-
'mri/transforms/talairach.xfm']),
664-
('nuintensitycor', ['mri/nu.mgz']),
665-
('normalization', ['mri/T1.mgz']),
666-
('skullstrip',
667-
['mri/brainmask.auto.mgz',
668-
'mri/brainmask.mgz']),
669-
# autorecon2
670-
('gcareg', ['mri/transforms/talairach.lta']),
671-
('canorm', ['mri/norm.mgz']),
672-
('careg', ['mri/transforms/talairach.m3z']),
673-
('careginv', ['mri/transforms/talairach.m3z.inv.x.mgz',
674-
'mri/transforms/talairach.m3z.inv.y.mgz',
675-
'mri/transforms/talairach.m3z.inv.z.mgz']),
676-
('rmneck', ['mri/nu_noneck.mgz']),
677-
('skull-lta', ['mri/transforms/talairach_with_skull_2.lta']),
678-
('calabel',
679-
['mri/aseg.auto_noCCseg.mgz', 'mri/aseg.auto.mgz', 'mri/aseg.mgz']),
680-
('normalization2', ['mri/brain.mgz']),
681-
('maskbfs', ['mri/brain.finalsurfs.mgz']),
682-
('segmentation', ['mri/wm.asegedit.mgz', 'mri/wm.mgz']),
683-
('fill', ['mri/filled.mgz']),
684-
('tessellate', ['surf/lh.orig.nofix', 'surf/rh.orig.nofix']),
685-
('smooth1', ['surf/lh.smoothwm.nofix', 'surf/rh.smoothwm.nofix']),
686-
('inflate1', ['surf/lh.inflated.nofix', 'surf/rh.inflated.nofix']),
687-
('qsphere', ['surf/lh.qsphere.nofix', 'surf/rh.qsphere.nofix']),
688-
('fix', ['surf/lh.orig', 'surf/rh.orig']),
689-
('white',
690-
['surf/lh.white',
691-
'surf/rh.white',
692-
'surf/lh.curv',
693-
'surf/rh.curv',
694-
'surf/lh.area',
695-
'surf/rh.area',
696-
'label/lh.cortex.label',
697-
'label/rh.cortex.label']),
698-
('smooth2', ['surf/lh.smoothwm', 'surf/rh.smoothwm']),
699-
('inflate2',
700-
['surf/lh.inflated',
701-
'surf/rh.inflated',
702-
'surf/lh.sulc',
703-
'surf/rh.sulc',
704-
'surf/lh.inflated.H',
705-
'surf/rh.inflated.H',
706-
'surf/lh.inflated.K',
707-
'surf/rh.inflated.K']),
708-
# autorecon3
709-
('sphere', ['surf/lh.sphere', 'surf/rh.sphere']),
710-
('surfreg', ['surf/lh.sphere.reg', 'surf/rh.sphere.reg']),
711-
('jacobian_white', ['surf/lh.jacobian_white',
712-
'surf/rh.jacobian_white']),
713-
('avgcurv', ['surf/lh.avg_curv', 'surf/rh.avg_curv']),
714-
('cortparc', ['label/lh.aparc.annot', 'label/rh.aparc.annot']),
715-
('pial',
716-
['surf/lh.pial',
717-
'surf/rh.pial',
718-
'surf/lh.curv.pial',
719-
'surf/rh.curv.pial',
720-
'surf/lh.area.pial',
721-
'surf/rh.area.pial',
722-
'surf/lh.thickness',
723-
'surf/rh.thickness']),
724-
('cortparc2', ['label/lh.aparc.a2009s.annot',
725-
'label/rh.aparc.a2009s.annot']),
726-
('parcstats2',
727-
['stats/lh.aparc.a2009s.stats',
728-
'stats/rh.aparc.a2009s.stats',
729-
'stats/aparc.annot.a2009s.ctab']),
730-
('cortribbon', ['mri/lh.ribbon.mgz', 'mri/rh.ribbon.mgz',
731-
'mri/ribbon.mgz']),
732-
('segstats', ['stats/aseg.stats']),
733-
('aparc2aseg', ['mri/aparc+aseg.mgz', 'mri/aparc.a2009s+aseg.mgz']),
734-
('wmparc', ['mri/wmparc.mgz', 'stats/wmparc.stats']),
735-
('balabels', ['BA.ctab', 'BA.thresh.ctab']),
736-
('label-exvivo-ec', ['label/lh.entorhinal_exvivo.label',
737-
'label/rh.entorhinal_exvivo.label'])]
669+
# Steps are based off of the recon-all tables [0,1] describing, inputs,
670+
# commands, and outputs of each step of the recon-all process,
671+
# controlled by flags.
672+
#
673+
# Each step is a 3-tuple containing (flag, [outputs], [inputs])
674+
# A step is considered complete if all of its outputs exist and are newer
675+
# than the inputs. An empty input list indicates input mtimes will not
676+
# be checked. This may need updating, if users are working with manually
677+
# edited files.
678+
#
679+
# [0] https://surfer.nmr.mgh.harvard.edu/fswiki/ReconAllTableStableV5.3
680+
# [1] https://surfer.nmr.mgh.harvard.edu/fswiki/ReconAllTableStableV6.0
681+
_autorecon1_steps = [
682+
('motioncor', ['mri/rawavg.mgz', 'mri/orig.mgz'], []),
683+
('talairach', ['mri/orig_nu.mgz',
684+
'mri/transforms/talairach.auto.xfm',
685+
'mri/transforms/talairach.xfm',
686+
# 'mri/transforms/talairach_avi.log',
687+
], []),
688+
('nuintensitycor', ['mri/nu.mgz'], []),
689+
('normalization', ['mri/T1.mgz'], []),
690+
('skullstrip', ['mri/talairach_with_skull.lta',
691+
'mri/brainmask.auto.mgz',
692+
'mri/brainmask.mgz'], []),
693+
]
694+
if LooseVersion(FSVersion) < LooseVersion("6.0.0"):
695+
_autorecon2_steps = [
696+
('gcareg', ['mri/transforms/talairach.lta'], []),
697+
('canorm', ['mri/norm.mgz'], []),
698+
('careg', ['mri/transforms/talairach.m3z'], []),
699+
('careginv', ['mri/transforms/talairach.m3z.inv.x.mgz',
700+
'mri/transforms/talairach.m3z.inv.y.mgz',
701+
'mri/transforms/talairach.m3z.inv.z.mgz',
702+
], []),
703+
('rmneck', ['mri/nu_noneck.mgz'], []),
704+
('skull-lta', ['mri/transforms/talairach_with_skull_2.lta'], []),
705+
('calabel', ['mri/aseg.auto_noCCseg.mgz',
706+
'mri/aseg.auto.mgz',
707+
'mri/aseg.mgz'], []),
708+
('normalization2', ['mri/brain.mgz'], []),
709+
('maskbfs', ['mri/brain.finalsurfs.mgz'], []),
710+
('segmentation', ['mri/wm.seg.mgz',
711+
'mri/wm.asegedit.mgz',
712+
'mri/wm.mgz'], []),
713+
('fill', ['mri/filled.mgz',
714+
# 'scripts/ponscc.cut.log',
715+
], []),
716+
('tessellate', ['surf/lh.orig.nofix', 'surf/rh.orig.nofix'], []),
717+
('smooth1', ['surf/lh.smoothwm.nofix', 'surf/rh.smoothwm.nofix'],
718+
[]),
719+
('inflate1', ['surf/lh.inflated.nofix', 'surf/rh.inflated.nofix'],
720+
[]),
721+
('qsphere', ['surf/lh.qsphere.nofix', 'surf/rh.qsphere.nofix'],
722+
[]),
723+
('fix', ['surf/lh.orig', 'surf/rh.orig'], []),
724+
('white', ['surf/lh.white', 'surf/rh.white',
725+
'surf/lh.curv', 'surf/rh.curv',
726+
'surf/lh.area', 'surf/rh.area',
727+
'label/lh.cortex.label', 'label/rh.cortex.label'], []),
728+
('smooth2', ['surf/lh.smoothwm', 'surf/rh.smoothwm'], []),
729+
('inflate2', ['surf/lh.inflated', 'surf/rh.inflated',
730+
'surf/lh.sulc', 'surf/rh.sulc',
731+
'surf/lh.inflated.H', 'surf/rh.inflated.H',
732+
'surf/lh.inflated.K', 'surf/rh.inflated.K'], []),
733+
]
734+
_autorecon3_steps = [
735+
('sphere', ['surf/lh.sphere', 'surf/rh.sphere'], []),
736+
('surfreg', ['surf/lh.sphere.reg', 'surf/rh.sphere.reg'], []),
737+
('jacobian_white', ['surf/lh.jacobian_white',
738+
'surf/rh.jacobian_white'], []),
739+
('avgcurv', ['surf/lh.avg_curv', 'surf/rh.avg_curv'], []),
740+
('cortparc', ['label/lh.aparc.annot', 'label/rh.aparc.annot'], []),
741+
('pial', ['surf/lh.pial', 'surf/rh.pial',
742+
'surf/lh.curv.pial', 'surf/rh.curv.pial',
743+
'surf/lh.area.pial', 'surf/rh.area.pial',
744+
'surf/lh.thickness', 'surf/rh.thickness'], []),
745+
('cortparc2', ['label/lh.aparc.a2009s.annot',
746+
'label/rh.aparc.a2009s.annot'], []),
747+
('parcstats2', ['stats/lh.aparc.a2009s.stats',
748+
'stats/rh.aparc.a2009s.stats',
749+
'stats/aparc.annot.a2009s.ctab'], []),
750+
('cortribbon', ['mri/lh.ribbon.mgz', 'mri/rh.ribbon.mgz',
751+
'mri/ribbon.mgz'], []),
752+
('segstats', ['stats/aseg.stats'], []),
753+
('aparc2aseg', ['mri/aparc+aseg.mgz',
754+
'mri/aparc.a2009s+aseg.mgz'], []),
755+
('wmparc', ['mri/wmparc.mgz', 'stats/wmparc.stats'], []),
756+
('balabels', ['BA.ctab', 'BA.thresh.ctab'], []),
757+
('label-exvivo-ec', ['label/lh.entorhinal_exvivo.label',
758+
'label/rh.entorhinal_exvivo.label'], []),
759+
]
760+
else:
761+
_autorecon2_steps = [
762+
('gcareg', ['mri/transforms/talairach.lta'], []),
763+
('canorm', ['mri/norm.mgz'], []),
764+
('careg', ['mri/transforms/talairach.m3z'], []),
765+
('calabel', ['mri/aseg.auto_noCCseg.mgz',
766+
'mri/aseg.auto.mgz',
767+
'mri/aseg.mgz'], []),
768+
('normalization2', ['mri/brain.mgz'], []),
769+
('maskbfs', ['mri/brain.finalsurfs.mgz'], []),
770+
('segmentation', ['mri/wm.seg.mgz',
771+
'mri/wm.asegedit.mgz',
772+
'mri/wm.mgz'], []),
773+
('fill', ['mri/filled.mgz',
774+
# 'scripts/ponscc.cut.log',
775+
], []),
776+
('tessellate', ['surf/lh.orig.nofix', 'surf/rh.orig.nofix'], []),
777+
('smooth1', ['surf/lh.smoothwm.nofix', 'surf/rh.smoothwm.nofix'],
778+
[]),
779+
('inflate1', ['surf/lh.inflated.nofix', 'surf/rh.inflated.nofix'],
780+
[]),
781+
('qsphere', ['surf/lh.qsphere.nofix', 'surf/rh.qsphere.nofix'],
782+
[]),
783+
('fix', ['surf/lh.orig', 'surf/rh.orig'], []),
784+
('white', ['surf/lh.white.preaparc', 'surf/rh.white.preaparc',
785+
'surf/lh.curv', 'surf/rh.curv',
786+
'surf/lh.area', 'surf/rh.area',
787+
'label/lh.cortex.label', 'label/rh.cortex.label'], []),
788+
('smooth2', ['surf/lh.smoothwm', 'surf/rh.smoothwm'], []),
789+
('inflate2', ['surf/lh.inflated', 'surf/rh.inflated',
790+
'surf/lh.sulc', 'surf/rh.sulc'], []),
791+
('curvHK', ['surf/lh.white.H', 'surf/rh.white.H',
792+
'surf/lh.white.K', 'surf/rh.white.K',
793+
'surf/lh.inflated.H', 'surf/rh.inflated.H',
794+
'surf/lh.inflated.K', 'surf/rh.inflated.K'], []),
795+
('curvstats', ['stats/lh.curv.stats', 'stats/rh.curv.stats'], []),
796+
]
797+
_autorecon3_steps = [
798+
('sphere', ['surf/lh.sphere', 'surf/rh.sphere'], []),
799+
('surfreg', ['surf/lh.sphere.reg', 'surf/rh.sphere.reg'], []),
800+
('jacobian_white', ['surf/lh.jacobian_white',
801+
'surf/rh.jacobian_white'], []),
802+
('avgcurv', ['surf/lh.avg_curv', 'surf/rh.avg_curv'], []),
803+
('cortparc', ['label/lh.aparc.annot', 'label/rh.aparc.annot'], []),
804+
('pial', ['surf/lh.pial', 'surf/rh.pial',
805+
'surf/lh.curv.pial', 'surf/rh.curv.pial',
806+
'surf/lh.area.pial', 'surf/rh.area.pial',
807+
'surf/lh.thickness', 'surf/rh.thickness'], []),
808+
('cortribbon', ['mri/lh.ribbon.mgz', 'mri/rh.ribbon.mgz',
809+
'mri/ribbon.mgz'], []),
810+
('parcstats', ['stats/lh.aparc.astats', 'stats/rh.aparc.stats',
811+
'stats/aparc.annot.ctab'], []),
812+
('cortparc2', ['label/lh.aparc.a2009s.annot',
813+
'label/rh.aparc.a2009s.annot'], []),
814+
('parcstats2', ['stats/lh.aparc.a2009s.stats',
815+
'stats/rh.aparc.a2009s.stats',
816+
'stats/aparc.annot.a2009s.ctab'], []),
817+
('cortparc3', ['label/lh.aparc.DKTatlas.annot',
818+
'label/rh.aparc.DKTatlas.annot'], []),
819+
('parcstats3', ['stats/lh.aparc.DKTatlas.stats',
820+
'stats/rh.aparc.DKTatlas.stats',
821+
'stats/aparc.annot.DKTatlas.ctab'], []),
822+
('pctsurfcon', ['surf/lh.w-g.pct.mgh', 'surf/rh.w-g.pct.mgh'], []),
823+
('hyporelabel', ['mri/aseg.presurf.hypos.mgz'], []),
824+
('aparc2aseg', ['mri/aparc+aseg.mgz',
825+
'mri/aparc.a2009s+aseg.mgz',
826+
'mri/aparc.DKTatlas+aseg.mgz'], []),
827+
('apas2aseg', ['mri/aseg.mgz'], ['mri/aparc+aseg.mgz']),
828+
('segstats', ['stats/aseg.stats'], []),
829+
('wmparc', ['mri/wmparc.mgz', 'stats/wmparc.stats'], []),
830+
('balabels', ['BA.ctab', 'BA.thresh.ctab',
831+
'label/lh.entorhinal_exvivo.label',
832+
'label/rh.entorhinal_exvivo.label'], []),
833+
]
834+
835+
_steps = _autorecon1_steps + _autorecon2_steps + _autorecon3_steps
738836

739837
def _gen_subjects_dir(self):
740838
return os.getcwd()
@@ -790,24 +888,21 @@ def cmdline(self):
790888
subjects_dir = self.inputs.subjects_dir
791889
if not isdefined(subjects_dir):
792890
subjects_dir = self._gen_subjects_dir()
793-
# cmd = cmd.replace(' -all ', ' -make all ')
794-
iflogger.info('Overriding recon-all directive')
891+
795892
flags = []
796-
directive = 'all'
797893
for idx, step in enumerate(self._steps):
798-
step, outfiles = step
799-
if all([os.path.exists(os.path.join(subjects_dir,
800-
self.inputs.subject_id, f)) for
801-
f in outfiles]):
802-
flags.append('-no%s' % step)
803-
if idx > 4:
804-
directive = 'autorecon2'
805-
elif idx > 23:
806-
directive = 'autorecon3'
807-
else:
808-
flags.append('-%s' % step)
809-
cmd = cmd.replace(' -%s ' % self.inputs.directive, ' -%s ' % directive)
894+
step, outfiles, infiles = step
895+
flag = '-{}'.format(step)
896+
noflag = '-no{}'.format(step)
897+
if flag in cmd or noflag in cmd:
898+
continue
899+
900+
subj_dir = os.path.join(subjects_dir, self.inputs.subject_id)
901+
if check_depends([os.path.join(subj_dir, f) for f in outfiles],
902+
[os.path.join(subj_dir, f) for f in infiles]):
903+
flags.append(noflag)
810904
cmd += ' ' + ' '.join(flags)
905+
811906
iflogger.info('resume recon-all : %s' % cmd)
812907
return cmd
813908

nipype/utils/filemanip.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,19 @@ def list_to_filename(filelist):
447447
else:
448448
return filelist[0]
449449

450+
451+
def check_depends(targets, dependencies):
452+
"""Return true if all targets exist and are newer than all dependencies.
453+
454+
An OSError will be raised if there are missing dependencies.
455+
"""
456+
tgts = filename_to_list(targets)
457+
deps = filename_to_list(dependencies)
458+
return all(map(os.path.exists, tgts)) and \
459+
min(map(os.path.getmtime, tgts)) > \
460+
max(list(map(os.path.getmtime, deps)) + [0])
461+
462+
450463
def save_json(filename, data):
451464
"""Save data to a json file
452465

0 commit comments

Comments
 (0)