From 35cc4c92dbd4bac721ddafeedad9dca53137d011 Mon Sep 17 00:00:00 2001 From: Jessey Wright Date: Tue, 2 Jul 2019 10:35:51 -0700 Subject: [PATCH 1/7] [WIP/ENH] Adds a new interface for AFNI's ``3dMema`` command Following Nipype's guidelines to write new interfaces. --- nipype/interfaces/afni/model.py | 305 ++++++++++++++++++++++++++++++++ 1 file changed, 305 insertions(+) diff --git a/nipype/interfaces/afni/model.py b/nipype/interfaces/afni/model.py index 2cccdfe869..9c16bfd00f 100644 --- a/nipype/interfaces/afni/model.py +++ b/nipype/interfaces/afni/model.py @@ -664,3 +664,308 @@ def _list_outputs(self): outputs[key] = os.path.abspath(self.inputs.get()[key]) return outputs + + +class MemaInputSpec(AFNICommandInputSpec): + #mandatory files + in_files = InputMultiPath( + File(exists=True), + desc='Specify the data for one of two test variables (either group,' + ' contrast/GLTs) A & B.', + argstr='-set ...', + mandatory=True + ) + +"""" + -set SETNAME \ + SUBJ_1 BETA_DSET T_DSET \ + SUBJ_2 BETA_DSET T_DSET \ + ... ... ... \ + SUBJ_N BETA_DSET T_DSET \ + Specify the data for one of two test variables (either group, + contrast/GLTs) A & B. + SETNAME is the name assigned to the set, which is only for the + user's information, and not used by the program. When + there are two groups, the 1st and 2nd datasets are + associated with the 1st and 2nd labels specified + through option -set, and the group difference is + the second group minus the first one, similar to + 3dttest but different from 3dttest++. + SUBJ_K is the label for the subject K whose datasets will be + listed next + BETA_DSET is the name of the dataset of the beta coefficient or GLT. + T_DSET is the name of the dataset containing the Tstat + corresponding to BETA_DSET. + To specify BETA_DSET, and T_DSET, you can use the standard AFNI + notation, which, in addition to sub-brick indices, now allows for + the use of sub-brick labels as selectors + e.g: -set Placebo Jane pb05.Jane.Regression+tlrc'[face#0_Beta]' \ + pb05.Jane.Regression+tlrc'[face#0_Tstat]' \ + + """ + + #conditionally mandatory arguments + groups = traits.Str( + desc='Name of 1 or 2 groups. This option must be used when comparing two groups.', + argstr='-groups %s' + ) + + """" + -groups GROUP1 [GROUP2]: Name of 1 or 2 groups. This option must be used + when comparing two groups. Default is one group + named 'G1'. The labels here are used to name + the sub-bricks in the output. When there are + two groups, the 1st and 2nd labels here are + associated with the 1st and 2nd datasets + specified respectively through option -set, + and their group difference is the second group + minus the first one, similar to 3dttest but + different from 3dttest++. + """ + + unequal_variance = traits.Bool( + desc='Model cross-subjects variability difference between' + ' GROUP1 and GROUP2 (heteroskedasticity). This option' + ' may NOT be invoked when covariate is present in the' + ' model.', + argstr='-unequal_variance' + ) + + equal_variance = traits.Bool( + desc='Assume same cross-subjects variability between GROUP1 and GROUP2' + ' (homoskedasticity) (Default)', + argstr='-equal_variance' + ) + + #Other arguments + cio = traits.Bool( + desc='use AFNIs C io functions', + argstr='-cio') + + contrast_name = traits.Str( + desc='no help available', + argstr='-contrast_name %s') + + covariates = File( + File(exists=True), + desc='Specify the name of a text file containing a table for the covariate(s).' + ' Each column in the file is treated as a separate covariate, and each row contains' + ' the values of these covariates for each subject. It is recommended to use the' + ' covariates file generated by 3dREMLfit.', + argstr='-covariates %s', + ) + + covariates_center = traits.Str( + desc='Centering rule for covariates. You can provide centering rules for each coveriate,' + ' or specify mean centering or no centering (using 0). If no specification is made each' + ' covariate will be centered about its own mean. See AFNI documentation for usage.', + argstr='covariates_center %s' + ) + + """" + -covariates_center COV_1=CEN_1 [COV_2=CEN_2 ... ]: (for 1 group) + -covariates_center COV_1=CEN_1.A CEN_1.B [COV_2=CEN_2.A CEN_2.B ... ]: + (for 2 groups) + where COV_K is the name assigned to the K-th covariate, + either from the header of the covariates file, or from the option + -covariates_name. This makes clear which center belongs to which + covariate. When two groups are used, you need to specify a center for + each of the groups (CEN_K.A, CEN_K.B). + Example: If you had covariates age, and weight, you would use: + -covariates_center age = 78 55 weight = 165 198 + """ + + covariates_model = traits.Tuple( + traits.Enum('same', 'different', desc='Specify the center'), + traits.Enum('same', 'different', desc='Specify the slope'), + desc='Specify whether to use the same or different intercepts for each of the covariates.' + ' Similarly for the slope.', + argstr='-covariates_model center=%s slope=%s' + ) + + """" + -covariates_model center=different/same slope=different/same: + Specify whether to use the same or different intercepts + for each of the covariates. Similarly for the slope. + """ + + covariates_name = traits.Str( + desc='Specify the name of each of the N covariates. Only needed if covariate file does' + ' not have a header. Default is to name covariates cov1, cov2, ...', + argstr='-coveriates_names %s') + + debugArgs = traits.Bool( + desc='Enable R to save parameters in a file called .3dMEMA.dbg.AFNI.args in the current' + ' directory for debugging.', + argstr='-dbArgs' + ) + + HKtest = traits.Bool( + desc='Perform Hartung-Knapp adjustment for the output t-statistic.' + 'This approach is more robust when the number of subjects' + 'is small, and is generally preferred. -KHtest is the default' + 'with t-statistic output.', + argstr='-HKtest' + ) + + num_threads = traits.Int( + desc='run the program with provided number of sub-processes', + argstr='-jobs %d', + nohash=True + ) + + mask = File( + desc='Process voxels from inside this mask only. Default is no masking', + argstr='-mask %s' + ) + + max_zeros = traits.Float( + desc='Do not compute statistics at any voxel that has' + ' more than MM zero beta coefficients or GLTs.' + 'Setting -max_zeros to 0.25 means process data only at voxels' + ' where no more than 1/4 of the data is missing. The default' + ' value is 0 (no missing values allowed). MM can be a positive' + ' integer less than the number of subjects, or a fraction' + ' between 0 and 1. Alternatively option -missing_data' + ' can be used to handle missing data.', + argstr='-max_zeros %f' + ) + + """" + -max_zeros MM: Do not compute statistics at any voxel that has + more than MM zero beta coefficients or GLTs. Voxels around + the edges of the group brain will not have data from + some of the subjects. Therefore, some of their beta's or + GLTs and t-stats are masked with 0. 3dMEMA can handle + missing data at those voxels but obviously too much + missing data is not good. Setting -max_zeros to 0.25 + means process data only at voxels where no more than 1/4 + of the data is missing. The default value is 0 (no + missing values allowed). MM can be a positive integer + less than the number of subjects, or a fraction + between 0 and 1. Alternatively option -missing_data + can be used to handle missing data. + """ + + missing_data = traits.Str( + desc='This option corrects for inflated statistics for the voxels where' + ' some subjects do not have any data available due to imperfect' + ' spatial alignment or other reasons. The absence of this option' + ' means no missing data will be assumed.', + argstr='-missing_data %s' + ) + + """" + -missing_data: This option corrects for inflated statistics for the voxels where + some subjects do not have any data available due to imperfect + spatial alignment or other reasons. The absence of this option + means no missing data will be assumed. Two formats of option + setting exist as shown below. + -missing_data 0: With this format the zero value at a voxel of each subject + will be interpreted as missing data. + -missing_data File1 [File2]: Information about missing data is specified + with file of 1 or 2 groups (the number 1 or 2 + and file order should be consistent with those + in option -groups). The voxel value of each file + indicates the number of sujects with missing data + in that group. + """ + + outliers = traits.Bool( + desc = 'Model outlier betas with a Laplace distribution of' + ' of subject-specific error. Default is -no_model_outliers', + argstr ='-model_outliers' + ) + + nonzeros = traits.Float( + desc = 'Do not compute statistics at any voxel that has' + ' less than NN non-zero beta values. This options is' + ' complimentary to -max_zeroes, and matches an option in' + ' the interactive 3dMEMA mode. NN is basically (number of' + ' unique subjects - MM). Alternatively option -missing_data' + ' can be used to handle missing data.', + argstr = '-n_nonzero %f' + ) + + noHKtest = traits.Bool( + desc='Do not make the Hartung-Knapp adjustment. -KHtest is' + ' the default with t-statistic output.', + argstr='-no_HKtest' + ) + + nooutliers = traits.Bool( + desc = 'No modeling of outlier betas/GLTs (Default)', + argstr ='-no_model_outliers' + ) + + noresidualZ = traits.Bool( + desc='Do not output residuals and their Z values (Default).', + argstr='-no_residual_Z' + ) + + out_file = File( + desc='output dataset prefix name', + argstr='-prefix %s') + + """" + -prefix PREFIX: Output prefix (just prefix, no view+suffix needed) + """ + + residualZ = traits.Bool( + desc='Output residuals and their Z values used in identifying' + ' outliers at voxel level. Default is -no_residual_Z', + argstr='-residual_Z' + ) + + rio = traits.Bool( + desc='use R's' io functions', + argstr='-rio') + + verbosity = traits.Int( + desc='An integer specifying verbosity level. 0 is quiet, 1+ is talkative.', + argstr='-verb %d') + + +class MemaOutputSpec(AFNICommandOutputSpec): + out_file = File( + desc='...', + exists=True + ) + args = File( + desc='Arguments file for debugging, generated if -debugArgs is set') + + +class Mema(AFNICommand): + """Description of 3dMEMA + + For complete details, see the `3dMEMA Documentation. + + + Examples + ======== + + >>> from nipype.interfaces import afni + >>> mema = afni.Mema() + >>> mema.inputs.in_files = [...] + ... + + """ + + _cmd = '3dMEMA' + input_spec = MemaInputSpec + output_spec = MemaOutputSpec + + def _parse_inputs(self, skip=None): + if skip is None: + skip = [] + return super(Mema, self)._parse_inputs(skip) + + def _list_outputs(self): + outputs = self.output_spec().get() + + for key in outputs.keys(): + if isdefined(self.inputs.get()[key]): + outputs[key] = os.path.abspath(self.inputs.get()[key]) + + return outputs + From 7c8380ece8915a85765b1d02c5d75eb7e831e2a8 Mon Sep 17 00:00:00 2001 From: Jessey Wright Date: Tue, 2 Jul 2019 11:07:50 -0700 Subject: [PATCH 2/7] fix(interfaces): amend some of the input traits --- nipype/interfaces/afni/model.py | 85 +++++++++++++++++---------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/nipype/interfaces/afni/model.py b/nipype/interfaces/afni/model.py index 9c16bfd00f..f520cc3f60 100644 --- a/nipype/interfaces/afni/model.py +++ b/nipype/interfaces/afni/model.py @@ -723,18 +723,16 @@ class MemaInputSpec(AFNICommandInputSpec): different from 3dttest++. """ - unequal_variance = traits.Bool( - desc='Model cross-subjects variability difference between' + equal_variance = traits.Bool( + True, + desc='Assume same cross-subjects variability between GROUP1 and GROUP2' + ' (homoskedasticity) (Default); or' + ' Model cross-subjects variability difference between' ' GROUP1 and GROUP2 (heteroskedasticity). This option' ' may NOT be invoked when covariate is present in the' ' model.', - argstr='-unequal_variance' - ) - - equal_variance = traits.Bool( - desc='Assume same cross-subjects variability between GROUP1 and GROUP2' - ' (homoskedasticity) (Default)', - argstr='-equal_variance' + argstr='-equal_variance', #-unequal_variance + usedefault=True, ) #Other arguments @@ -756,25 +754,23 @@ class MemaInputSpec(AFNICommandInputSpec): ) covariates_center = traits.Str( - desc='Centering rule for covariates. You can provide centering rules for each coveriate,' - ' or specify mean centering or no centering (using 0). If no specification is made each' - ' covariate will be centered about its own mean. See AFNI documentation for usage.', + desc="""\ +Centering rule for covariates. You can provide centering rules for each coveriate, +or specify mean centering or no centering (using 0). If no specification is made each +covariate will be centered about its own mean. +-covariates_center COV_1=CEN_1 [COV_2=CEN_2 ... ]: (for 1 group) +-covariates_center COV_1=CEN_1.A CEN_1.B [COV_2=CEN_2.A CEN_2.B ... ]: + (for 2 groups) + where COV_K is the name assigned to the K-th covariate, + either from the header of the covariates file, or from the option + -covariates_name. This makes clear which center belongs to which + covariate. When two groups are used, you need to specify a center for + each of the groups (CEN_K.A, CEN_K.B). + Example: If you had covariates age, and weight, you would use: + -covariates_center age = 78 55 weight = 165 198""", argstr='covariates_center %s' ) - """" - -covariates_center COV_1=CEN_1 [COV_2=CEN_2 ... ]: (for 1 group) - -covariates_center COV_1=CEN_1.A CEN_1.B [COV_2=CEN_2.A CEN_2.B ... ]: - (for 2 groups) - where COV_K is the name assigned to the K-th covariate, - either from the header of the covariates file, or from the option - -covariates_name. This makes clear which center belongs to which - covariate. When two groups are used, you need to specify a center for - each of the groups (CEN_K.A, CEN_K.B). - Example: If you had covariates age, and weight, you would use: - -covariates_center age = 78 55 weight = 165 198 - """ - covariates_model = traits.Tuple( traits.Enum('same', 'different', desc='Specify the center'), traits.Enum('same', 'different', desc='Specify the slope'), @@ -789,10 +785,10 @@ class MemaInputSpec(AFNICommandInputSpec): for each of the covariates. Similarly for the slope. """ - covariates_name = traits.Str( + covariates_name = traits.List(Str, desc='Specify the name of each of the N covariates. Only needed if covariate file does' ' not have a header. Default is to name covariates cov1, cov2, ...', - argstr='-coveriates_names %s') + argstr='-covariates_names %s') debugArgs = traits.Bool( desc='Enable R to save parameters in a file called .3dMEMA.dbg.AFNI.args in the current' @@ -819,7 +815,7 @@ class MemaInputSpec(AFNICommandInputSpec): argstr='-mask %s' ) - max_zeros = traits.Float( + max_zeros = traits.Range( # Please revise all the other possible settings desc='Do not compute statistics at any voxel that has' ' more than MM zero beta coefficients or GLTs.' 'Setting -max_zeros to 0.25 means process data only at voxels' @@ -828,7 +824,9 @@ class MemaInputSpec(AFNICommandInputSpec): ' integer less than the number of subjects, or a fraction' ' between 0 and 1. Alternatively option -missing_data' ' can be used to handle missing data.', - argstr='-max_zeros %f' + min=0.0, max=1.0, + argstr='-max_zeros %f', + xor=['missing_data'], ) """" @@ -847,13 +845,16 @@ class MemaInputSpec(AFNICommandInputSpec): can be used to handle missing data. """ - missing_data = traits.Str( + missing_data = traits.Either( + 0, + traits.List(File(exists=True), minlen=1, maxlen=2,), desc='This option corrects for inflated statistics for the voxels where' ' some subjects do not have any data available due to imperfect' ' spatial alignment or other reasons. The absence of this option' ' means no missing data will be assumed.', - argstr='-missing_data %s' - ) + argstr='-missing_data %s', + xor=['max_zeros'] + ) """" -missing_data: This option corrects for inflated statistics for the voxels where @@ -871,7 +872,7 @@ class MemaInputSpec(AFNICommandInputSpec): in that group. """ - outliers = traits.Bool( + outliers = traits.Bool( # Please combine with -no_model_outliers desc = 'Model outlier betas with a Laplace distribution of' ' of subject-specific error. Default is -no_model_outliers', argstr ='-model_outliers' @@ -880,14 +881,15 @@ class MemaInputSpec(AFNICommandInputSpec): nonzeros = traits.Float( desc = 'Do not compute statistics at any voxel that has' ' less than NN non-zero beta values. This options is' - ' complimentary to -max_zeroes, and matches an option in' + ' complimentary to -max_zeros, and matches an option in' ' the interactive 3dMEMA mode. NN is basically (number of' ' unique subjects - MM). Alternatively option -missing_data' ' can be used to handle missing data.', - argstr = '-n_nonzero %f' + argstr = '-n_nonzero %f', + xor=['missing_data'] ) - noHKtest = traits.Bool( + noHKtest = traits.Bool( # Please combine with -KHtest desc='Do not make the Hartung-Knapp adjustment. -KHtest is' ' the default with t-statistic output.', argstr='-no_HKtest' @@ -898,7 +900,7 @@ class MemaInputSpec(AFNICommandInputSpec): argstr ='-no_model_outliers' ) - noresidualZ = traits.Bool( + noresidualZ = traits.Bool( # Combine with -residualZ desc='Do not output residuals and their Z values (Default).', argstr='-no_residual_Z' ) @@ -918,10 +920,13 @@ class MemaInputSpec(AFNICommandInputSpec): ) rio = traits.Bool( - desc='use R's' io functions', + desc='use R\'s io functions', argstr='-rio') - verbosity = traits.Int( + verbosity = traits.Range( + 1, + usedefault=True, + min=0, desc='An integer specifying verbosity level. 0 is quiet, 1+ is talkative.', argstr='-verb %d') @@ -932,7 +937,7 @@ class MemaOutputSpec(AFNICommandOutputSpec): exists=True ) args = File( - desc='Arguments file for debugging, generated if -debugArgs is set') + desc='Arguments file for debugging, generated if -dbArgs is set') class Mema(AFNICommand): From 0ed0331b1bbb6ccc700dbfa1a72da25ed53dd101 Mon Sep 17 00:00:00 2001 From: oesteban Date: Tue, 2 Jul 2019 12:01:49 -0700 Subject: [PATCH 3/7] fix(interfaces): first pass at writing traits of 3dMEMA --- nipype/interfaces/afni/model.py | 312 +++++++++++++++----------------- nipype/interfaces/base/core.py | 9 +- 2 files changed, 149 insertions(+), 172 deletions(-) diff --git a/nipype/interfaces/afni/model.py b/nipype/interfaces/afni/model.py index f520cc3f60..d120d74faa 100644 --- a/nipype/interfaces/afni/model.py +++ b/nipype/interfaces/afni/model.py @@ -13,7 +13,7 @@ import os from ..base import (CommandLineInputSpec, CommandLine, Directory, TraitedSpec, - traits, isdefined, File, InputMultiPath, Undefined, Str) + traits, isdefined, File, InputMultiObject, Undefined, Str) from ...external.due import BibTeX from .base import (AFNICommandBase, AFNICommand, AFNICommandInputSpec, @@ -21,7 +21,7 @@ class DeconvolveInputSpec(AFNICommandInputSpec): - in_files = InputMultiPath( + in_files = InputMultiObject( File(exists=True), desc='filenames of 3D+time input datasets. More than one filename can ' 'be given and the datasets will be auto-catenated in time. ' @@ -309,7 +309,7 @@ def _list_outputs(self): class RemlfitInputSpec(AFNICommandInputSpec): # mandatory files - in_files = InputMultiPath( + in_files = InputMultiObject( File(exists=True), desc='Read time series dataset', argstr='-input "%s"', @@ -356,7 +356,7 @@ class RemlfitInputSpec(AFNICommandInputSpec): '(only with \'mask\' or \'automask\' options).', argstr='-STATmask %s', exists=True) - addbase = InputMultiPath( + addbase = InputMultiObject( File( exists=True, desc='file containing columns to add to regression matrix'), @@ -367,7 +367,7 @@ class RemlfitInputSpec(AFNICommandInputSpec): copyfile=False, sep=" ", argstr='-addbase %s') - slibase = InputMultiPath( + slibase = InputMultiObject( File( exists=True, desc='file containing columns to add to regression matrix'), @@ -384,7 +384,7 @@ class RemlfitInputSpec(AFNICommandInputSpec): 'will slow the program down, and make it use a lot more memory ' '(to hold all the matrix stuff).', argstr='-slibase %s') - slibase_sm = InputMultiPath( + slibase_sm = InputMultiObject( File( exists=True, desc='file containing columns to add to regression matrix'), @@ -667,48 +667,49 @@ def _list_outputs(self): class MemaInputSpec(AFNICommandInputSpec): - #mandatory files - in_files = InputMultiPath( - File(exists=True), - desc='Specify the data for one of two test variables (either group,' - ' contrast/GLTs) A & B.', - argstr='-set ...', - mandatory=True - ) - -"""" - -set SETNAME \ - SUBJ_1 BETA_DSET T_DSET \ - SUBJ_2 BETA_DSET T_DSET \ - ... ... ... \ - SUBJ_N BETA_DSET T_DSET \ - Specify the data for one of two test variables (either group, - contrast/GLTs) A & B. - SETNAME is the name assigned to the set, which is only for the - user's information, and not used by the program. When - there are two groups, the 1st and 2nd datasets are - associated with the 1st and 2nd labels specified - through option -set, and the group difference is - the second group minus the first one, similar to - 3dttest but different from 3dttest++. - SUBJ_K is the label for the subject K whose datasets will be - listed next - BETA_DSET is the name of the dataset of the beta coefficient or GLT. - T_DSET is the name of the dataset containing the Tstat - corresponding to BETA_DSET. - To specify BETA_DSET, and T_DSET, you can use the standard AFNI - notation, which, in addition to sub-brick indices, now allows for - the use of sub-brick labels as selectors - e.g: -set Placebo Jane pb05.Jane.Regression+tlrc'[face#0_Beta]' \ - pb05.Jane.Regression+tlrc'[face#0_Tstat]' \ + # mandatory inputs + subject_trait = traits.Tuple( + Str, File(exists=True), File(exists=True), Str(''), Str('') + ) - """ + sets = traits.List( + traits.Tuple(Str(minlen=1), traits.List(subject_trait, minlen=2)), + desc="""\ +Specify the data for one of two test variables (either group, contrast/GLTs) A & B. + +SETNAME is the name assigned to the set, which is only for the + user's information, and not used by the program. When + there are two groups, the 1st and 2nd datasets are + associated with the 1st and 2nd labels specified + through option -set, and the group difference is + the second group minus the first one, similar to + 3dttest but different from 3dttest++. +SUBJ_K is the label for the subject K whose datasets will be + listed next +BETA_DSET is the name of the dataset of the beta coefficient or GLT. +T_DSET is the name of the dataset containing the Tstat + corresponding to BETA_DSET. + To specify BETA_DSET, and T_DSET, you can use the standard AFNI + notation, which, in addition to sub-brick indices, now allows for + the use of sub-brick labels as selectors +e.g: -set Placebo Jane pb05.Jane.Regression+tlrc'[face#0_Beta]' + pb05.Jane.Regression+tlrc'[face#0_Tstat]' +""", + argstr='-set %s ...', + mandatory=True, + minlen=1, + maxlen=2, + ) - #conditionally mandatory arguments - groups = traits.Str( + # conditionally mandatory arguments + groups = traits.List( + ['G1'], + Str(minlen=1), desc='Name of 1 or 2 groups. This option must be used when comparing two groups.', - argstr='-groups %s' - ) + argstr='-groups %s', + minlen=1, + maxlen=2, + ) """" -groups GROUP1 [GROUP2]: Name of 1 or 2 groups. This option must be used @@ -725,17 +726,18 @@ class MemaInputSpec(AFNICommandInputSpec): equal_variance = traits.Bool( True, - desc='Assume same cross-subjects variability between GROUP1 and GROUP2' - ' (homoskedasticity) (Default); or' - ' Model cross-subjects variability difference between' - ' GROUP1 and GROUP2 (heteroskedasticity). This option' - ' may NOT be invoked when covariate is present in the' - ' model.', - argstr='-equal_variance', #-unequal_variance usedefault=True, - ) - - #Other arguments + argstr=['-unequal_variance', '-equal_variance'], + xor=['covariates'], + desc="""\ +[-equal_variance] Assume same cross-subjects variability between GROUP1 and GROUP2 +(homoskedasticity) (Default); or [-unequal_variance] Model cross-subjects variability +difference between GROUP1 and GROUP2 (heteroskedasticity). +This option may NOT be invoked when covariate is present in the +model.""", + ) + + # Other arguments cio = traits.Bool( desc='use AFNIs C io functions', argstr='-cio') @@ -751,17 +753,17 @@ class MemaInputSpec(AFNICommandInputSpec): ' the values of these covariates for each subject. It is recommended to use the' ' covariates file generated by 3dREMLfit.', argstr='-covariates %s', - ) + ) covariates_center = traits.Str( desc="""\ -Centering rule for covariates. You can provide centering rules for each coveriate, +Centering rule for covariates. You can provide centering rules for each coveriate, or specify mean centering or no centering (using 0). If no specification is made each covariate will be centered about its own mean. --covariates_center COV_1=CEN_1 [COV_2=CEN_2 ... ]: (for 1 group) --covariates_center COV_1=CEN_1.A CEN_1.B [COV_2=CEN_2.A CEN_2.B ... ]: - (for 2 groups) - where COV_K is the name assigned to the K-th covariate, +-covariates_center COV_1=CEN_1 [COV_2=CEN_2 ... ]: (for 1 group) +-covariates_center COV_1=CEN_1.A CEN_1.B [COV_2=CEN_2.A CEN_2.B ... ]: + (for 2 groups) + where COV_K is the name assigned to the K-th covariate, either from the header of the covariates file, or from the option -covariates_name. This makes clear which center belongs to which covariate. When two groups are used, you need to specify a center for @@ -769,23 +771,18 @@ class MemaInputSpec(AFNICommandInputSpec): Example: If you had covariates age, and weight, you would use: -covariates_center age = 78 55 weight = 165 198""", argstr='covariates_center %s' - ) + ) covariates_model = traits.Tuple( - traits.Enum('same', 'different', desc='Specify the center'), - traits.Enum('same', 'different', desc='Specify the slope'), + traits.Enum('same', 'different', desc='Specify the center'), + traits.Enum('same', 'different', desc='Specify the slope'), desc='Specify whether to use the same or different intercepts for each of the covariates.' - ' Similarly for the slope.', + ' Similarly for the slope.', argstr='-covariates_model center=%s slope=%s' - ) - - """" - -covariates_model center=different/same slope=different/same: - Specify whether to use the same or different intercepts - for each of the covariates. Similarly for the slope. - """ + ) - covariates_name = traits.List(Str, + covariates_name = traits.List( + Str(minlen=1), desc='Specify the name of each of the N covariates. Only needed if covariate file does' ' not have a header. Default is to name covariates cov1, cov2, ...', argstr='-covariates_names %s') @@ -794,131 +791,109 @@ class MemaInputSpec(AFNICommandInputSpec): desc='Enable R to save parameters in a file called .3dMEMA.dbg.AFNI.args in the current' ' directory for debugging.', argstr='-dbArgs' - ) + ) - HKtest = traits.Bool( - desc='Perform Hartung-Knapp adjustment for the output t-statistic.' - 'This approach is more robust when the number of subjects' - 'is small, and is generally preferred. -KHtest is the default' - 'with t-statistic output.', - argstr='-HKtest' - ) + hk_test = traits.Bool( + desc="""\ +Perform Hartung-Knapp adjustment for the output t-statistic. \ +This approach is more robust when the number of subjects \ +is small, and is generally preferred. -KHtest is the default \ +with t-statistic output.""", + argstr=['-no_HKtest', '-HKtest'], + ) num_threads = traits.Int( desc='run the program with provided number of sub-processes', argstr='-jobs %d', nohash=True - ) + ) mask = File( + exists=True, desc='Process voxels from inside this mask only. Default is no masking', argstr='-mask %s' - ) + ) max_zeros = traits.Range( # Please revise all the other possible settings - desc='Do not compute statistics at any voxel that has' - ' more than MM zero beta coefficients or GLTs.' - 'Setting -max_zeros to 0.25 means process data only at voxels' - ' where no more than 1/4 of the data is missing. The default' - ' value is 0 (no missing values allowed). MM can be a positive' - ' integer less than the number of subjects, or a fraction' - ' between 0 and 1. Alternatively option -missing_data' - ' can be used to handle missing data.', + desc="""\ +Do not compute statistics at any voxel that has \ +more than MM zero beta coefficients or GLTs. Voxels around \ +the edges of the group brain will not have data from \ +some of the subjects. Therefore, some of their beta's or \ +GLTs and t-stats are masked with 0. 3dMEMA can handle \ +missing data at those voxels but obviously too much \ +missing data is not good. Setting -max_zeros to 0.25 \ +means process data only at voxels where no more than 1/4 \ +of the data is missing. The default value is 0 (no \ +missing values allowed). MM can be a positive integer \ +less than the number of subjects, or a fraction \ +between 0 and 1. Alternatively option -missing_data \ +can be used to handle missing data.""", + min=0.0, max=1.0, argstr='-max_zeros %f', xor=['missing_data'], - ) - - """" - -max_zeros MM: Do not compute statistics at any voxel that has - more than MM zero beta coefficients or GLTs. Voxels around - the edges of the group brain will not have data from - some of the subjects. Therefore, some of their beta's or - GLTs and t-stats are masked with 0. 3dMEMA can handle - missing data at those voxels but obviously too much - missing data is not good. Setting -max_zeros to 0.25 - means process data only at voxels where no more than 1/4 - of the data is missing. The default value is 0 (no - missing values allowed). MM can be a positive integer - less than the number of subjects, or a fraction - between 0 and 1. Alternatively option -missing_data - can be used to handle missing data. - """ + ) missing_data = traits.Either( 0, traits.List(File(exists=True), minlen=1, maxlen=2,), - desc='This option corrects for inflated statistics for the voxels where' - ' some subjects do not have any data available due to imperfect' - ' spatial alignment or other reasons. The absence of this option' - ' means no missing data will be assumed.', + desc="""\ +This option corrects for inflated statistics for the voxels where +some subjects do not have any data available due to imperfect +spatial alignment or other reasons. The absence of this option +means no missing data will be assumed.""", argstr='-missing_data %s', xor=['max_zeros'] ) - """" - -missing_data: This option corrects for inflated statistics for the voxels where - some subjects do not have any data available due to imperfect - spatial alignment or other reasons. The absence of this option - means no missing data will be assumed. Two formats of option - setting exist as shown below. - -missing_data 0: With this format the zero value at a voxel of each subject - will be interpreted as missing data. - -missing_data File1 [File2]: Information about missing data is specified - with file of 1 or 2 groups (the number 1 or 2 - and file order should be consistent with those - in option -groups). The voxel value of each file - indicates the number of sujects with missing data - in that group. - """ - - outliers = traits.Bool( # Please combine with -no_model_outliers - desc = 'Model outlier betas with a Laplace distribution of' - ' of subject-specific error. Default is -no_model_outliers', - argstr ='-model_outliers' - ) + # -missing_data: This option corrects for inflated statistics for the voxels where + # some subjects do not have any data available due to imperfect + # spatial alignment or other reasons. The absence of this option + # means no missing data will be assumed. Two formats of option + # setting exist as shown below. + # -missing_data 0: With this format the zero value at a voxel of each subject + # will be interpreted as missing data. + # -missing_data File1 [File2]: Information about missing data is specified + # with file of 1 or 2 groups (the number 1 or 2 + # and file order should be consistent with those + # in option -groups). The voxel value of each file + # indicates the number of sujects with missing data + # in that group. + + outliers = traits.Bool( + False, + usedefault=True, + desc='Model outlier betas with a Laplace distribution of ' + 'subject-specific error. Default is -no_model_outliers', + argstr=['-no_model_outliers', '-model_outliers'], + ) nonzeros = traits.Float( - desc = 'Do not compute statistics at any voxel that has' - ' less than NN non-zero beta values. This options is' - ' complimentary to -max_zeros, and matches an option in' - ' the interactive 3dMEMA mode. NN is basically (number of' - ' unique subjects - MM). Alternatively option -missing_data' - ' can be used to handle missing data.', - argstr = '-n_nonzero %f', + desc="""\ +Do not compute statistics at any voxel that has \ +less than NN non-zero beta values. This options is \ +complimentary to -max_zeros, and matches an option in \ +the interactive 3dMEMA mode. NN is basically (number of \ +unique subjects - MM). Alternatively option -missing_data \ +can be used to handle missing data.""", + argstr='-n_nonzero %f', xor=['missing_data'] - ) - - noHKtest = traits.Bool( # Please combine with -KHtest - desc='Do not make the Hartung-Knapp adjustment. -KHtest is' - ' the default with t-statistic output.', - argstr='-no_HKtest' - ) - - nooutliers = traits.Bool( - desc = 'No modeling of outlier betas/GLTs (Default)', - argstr ='-no_model_outliers' - ) + ) - noresidualZ = traits.Bool( # Combine with -residualZ - desc='Do not output residuals and their Z values (Default).', - argstr='-no_residual_Z' - ) + residualZ = traits.Bool( + False, + usedefault=True, + desc='Output residuals and their Z values used in identifying ' + 'outliers at voxel level. Default is -no_residual_Z', + argstr=['-no_residual_Z', '-residual_Z'] + ) + # -prefix PREFIX: Output prefix (just prefix, no view+suffix needed) out_file = File( desc='output dataset prefix name', argstr='-prefix %s') - """" - -prefix PREFIX: Output prefix (just prefix, no view+suffix needed) - """ - - residualZ = traits.Bool( - desc='Output residuals and their Z values used in identifying' - ' outliers at voxel level. Default is -no_residual_Z', - argstr='-residual_Z' - ) - rio = traits.Bool( desc='use R\'s io functions', argstr='-rio') @@ -935,7 +910,7 @@ class MemaOutputSpec(AFNICommandOutputSpec): out_file = File( desc='...', exists=True - ) + ) args = File( desc='Arguments file for debugging, generated if -dbArgs is set') @@ -953,7 +928,7 @@ class Mema(AFNICommand): >>> mema = afni.Mema() >>> mema.inputs.in_files = [...] ... - + """ _cmd = '3dMEMA' @@ -973,4 +948,3 @@ def _list_outputs(self): outputs[key] = os.path.abspath(self.inputs.get()[key]) return outputs - diff --git a/nipype/interfaces/base/core.py b/nipype/interfaces/base/core.py index 77ab6cf398..d7e38b5428 100644 --- a/nipype/interfaces/base/core.py +++ b/nipype/interfaces/base/core.py @@ -203,10 +203,10 @@ def _check_requires(self, spec, name, value): ] if any(values) and isdefined(value): if len(values) > 1: - fmt = ("%s requires values for inputs %s because '%s' is set. " + fmt = ("%s requires values for inputs %s because '%s' is set. " "For a list of required inputs, see %s.help()") else: - fmt = ("%s requires a value for input %s because '%s' is set. " + fmt = ("%s requires a value for input %s because '%s' is set. " "For a list of required inputs, see %s.help()") msg = fmt % (self.__class__.__name__, ', '.join("'%s'" % req for req in spec.requires), @@ -766,7 +766,10 @@ def _format_arg(self, name, trait_spec, value): """ argstr = trait_spec.argstr iflogger.debug('%s_%s', name, value) - if trait_spec.is_trait_type(traits.Bool) and "%" not in argstr: + if trait_spec.is_trait_type(traits.Bool) and isinstance(argstr, (list, tuple, dict)): + return argstr[value] + + elif trait_spec.is_trait_type(traits.Bool) and "%" not in argstr: # Boolean options have no format string. Just append options if True. return argstr if value else None # traits.Either turns into traits.TraitCompound and does not have any From a204ce0942137437d0b4e3eb81cf701ce6bd744b Mon Sep 17 00:00:00 2001 From: oesteban Date: Tue, 2 Jul 2019 12:45:26 -0700 Subject: [PATCH 4/7] fix(interfaces): update 3dMEMA's sets input --- nipype/interfaces/afni/model.py | 49 +++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/nipype/interfaces/afni/model.py b/nipype/interfaces/afni/model.py index d120d74faa..b185d9c3b3 100644 --- a/nipype/interfaces/afni/model.py +++ b/nipype/interfaces/afni/model.py @@ -668,12 +668,17 @@ def _list_outputs(self): class MemaInputSpec(AFNICommandInputSpec): # mandatory inputs - subject_trait = traits.Tuple( - Str, File(exists=True), File(exists=True), Str(''), Str('') + _subject_trait = traits.Either( + traits.Tuple( + Str(minlen=1), File(exists=True), File(exists=True) + ), + traits.Tuple( + Str(minlen=1), File(exists=True), File(exists=True), Str(''), Str('') + ) ) sets = traits.List( - traits.Tuple(Str(minlen=1), traits.List(subject_trait, minlen=2)), + traits.Tuple(Str(minlen=1), traits.List(_subject_trait, minlen=2)), desc="""\ Specify the data for one of two test variables (either group, contrast/GLTs) A & B. @@ -695,7 +700,7 @@ class MemaInputSpec(AFNICommandInputSpec): e.g: -set Placebo Jane pb05.Jane.Regression+tlrc'[face#0_Beta]' pb05.Jane.Regression+tlrc'[face#0_Tstat]' """, - argstr='-set %s ...', + argstr='-set %s...', mandatory=True, minlen=1, maxlen=2, @@ -814,7 +819,7 @@ class MemaInputSpec(AFNICommandInputSpec): argstr='-mask %s' ) - max_zeros = traits.Range( # Please revise all the other possible settings + max_zeros = traits.Range( # Please revise all the other possible settings\ desc="""\ Do not compute statistics at any voxel that has \ more than MM zero beta coefficients or GLTs. Voxels around \ @@ -829,8 +834,7 @@ class MemaInputSpec(AFNICommandInputSpec): less than the number of subjects, or a fraction \ between 0 and 1. Alternatively option -missing_data \ can be used to handle missing data.""", - - min=0.0, max=1.0, + low=0.0, high=1.0, argstr='-max_zeros %f', xor=['missing_data'], ) @@ -899,11 +903,12 @@ class MemaInputSpec(AFNICommandInputSpec): argstr='-rio') verbosity = traits.Range( - 1, + value=1, usedefault=True, - min=0, + low=0, desc='An integer specifying verbosity level. 0 is quiet, 1+ is talkative.', - argstr='-verb %d') + argstr='-verb %d' + ) class MemaOutputSpec(AFNICommandOutputSpec): @@ -919,7 +924,7 @@ class Mema(AFNICommand): """Description of 3dMEMA For complete details, see the `3dMEMA Documentation. - + `__ Examples ======== @@ -935,6 +940,28 @@ class Mema(AFNICommand): input_spec = MemaInputSpec output_spec = MemaOutputSpec + def _format_arg(self, name, trait_spec, value): + if name == "sets": + self._n_sets = len(value) + formatted_values = [] + for setname, setopts in value: + formatted_subject = [] + for this_set in setopts: + if len(this_set) == 4: + subid, beta_file, ttst_file, beta_opts, ttst_opts = this_set + if beta_opts: + beta_file = "%s'%s'" % (beta_file, beta_opts) + if ttst_opts: + ttst_file = "%s'%s'" % (ttst_file, ttst_opts) + else: + subid, beta_file, ttst_file = this_set + formatted_subject.append(' '.join((subid, beta_file, ttst_file))) + formatted_values.append(' '.join([setname] + formatted_subject)) + value = formatted_values + + return super(Mema, self)._format_arg(name, trait_spec, value) + + def _parse_inputs(self, skip=None): if skip is None: skip = [] From 1b0b9f4a0d8a3a2dc1b53a965611b14985dfc942 Mon Sep 17 00:00:00 2001 From: Jessey Wright Date: Tue, 2 Jul 2019 13:56:29 -0700 Subject: [PATCH 5/7] Added examples to Mema Interface --- nipype/interfaces/afni/model.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/nipype/interfaces/afni/model.py b/nipype/interfaces/afni/model.py index b185d9c3b3..6d3032dd04 100644 --- a/nipype/interfaces/afni/model.py +++ b/nipype/interfaces/afni/model.py @@ -916,6 +916,7 @@ class MemaOutputSpec(AFNICommandOutputSpec): desc='...', exists=True ) + args = File( desc='Arguments file for debugging, generated if -dbArgs is set') @@ -931,8 +932,32 @@ class Mema(AFNICommand): >>> from nipype.interfaces import afni >>> mema = afni.Mema() - >>> mema.inputs.in_files = [...] - ... + >>> mema.inputs.sets = ['analysis_name', [[subject_1, s1_betas, s1_t, numb1, numt1], [subject2, s2_bets, s2_ts, numb2, numt2], ...]] + >>> mema.inputs.max_zeros = 4 + >>> mema.inputs.model_outliers = True + >>> mema.inputs.residual_z = True + >>> mema.inputs.out_file = 'Results.BRIK' + + >>> from nipype.interfaces import afni + >>> mema = afni.Mema() + >>> mema.inputs.missing_data['File1', 'File2'] + >>> mema.inputs.sets = ['analysis_name', [[subject_1, s1_betas, s1_ts], [subject2, s2_bets, s2_ts], ...]] + >>> mema.inputs.out_file = 'Results.BRIK' + + >>> from nipype.interfaces import afni + >>> mema = afni.Mema() + >>> mema.inputs.groups = ['group1', 'group2'] + >>> mema.inputs.sets = ['analysis1_name', [[subject_1, s1_betas, s1_ts], [subject2, s2_bets, s2_ts], ...]] + >>> mema.inputs.sets = ['analysis2_name', [[subject_1, s1_betas, s1_ts], [subject2, s2_bets, s2_ts], ...]] + >>> mema.inputs.n_nonzero = 18 + >>> mema.inputs.hktest = True + >>> mema.inputs.outliers = True + >>> mema.inputs.equal_variance = False + >>> mema.inputs.residual_z = True + >>> mema.inputs.covariates = 'CovFile.txt' + >>> mema.inputs.covariates_center = 'age = 25 13 weight = 100 150' + >>> mema.inputs.covariates_model = ['different', 'same'] + >>> mema.inputs.out_file = 'Results.BRIK' """ From 1a28ec3c91feeb7a8042f8d468261713587d6b9b Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Fri, 23 Aug 2019 20:20:11 -0700 Subject: [PATCH 6/7] enh: revise some of the tests, add afni module import --- nipype/interfaces/afni/__init__.py | 2 +- nipype/interfaces/afni/model.py | 43 +++++++++++++++++++----------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/nipype/interfaces/afni/__init__.py b/nipype/interfaces/afni/__init__.py index 7af80059f2..dff9e1b8f2 100644 --- a/nipype/interfaces/afni/__init__.py +++ b/nipype/interfaces/afni/__init__.py @@ -21,4 +21,4 @@ LocalBistat, Localstat, MaskTool, Merge, Notes, NwarpApply, NwarpAdjust, NwarpCat, OneDToolPy, Refit, ReHo, Resample, TCat, TCatSubBrick, TStat, To3D, Unifize, Undump, ZCutUp, GCOR, Zcat, Zeropad) -from .model import (Deconvolve, Remlfit, Synthesize) +from .model import (Deconvolve, Remlfit, Synthesize, Mema) diff --git a/nipype/interfaces/afni/model.py b/nipype/interfaces/afni/model.py index 6d3032dd04..19d41aed46 100644 --- a/nipype/interfaces/afni/model.py +++ b/nipype/interfaces/afni/model.py @@ -678,7 +678,7 @@ class MemaInputSpec(AFNICommandInputSpec): ) sets = traits.List( - traits.Tuple(Str(minlen=1), traits.List(_subject_trait, minlen=2)), + traits.Tuple(Str(minlen=1), traits.List(_subject_trait, minlen=1)), desc="""\ Specify the data for one of two test variables (either group, contrast/GLTs) A & B. @@ -698,7 +698,7 @@ class MemaInputSpec(AFNICommandInputSpec): notation, which, in addition to sub-brick indices, now allows for the use of sub-brick labels as selectors e.g: -set Placebo Jane pb05.Jane.Regression+tlrc'[face#0_Beta]' - pb05.Jane.Regression+tlrc'[face#0_Tstat]' + pb05.Jane.Regression+tlrc'[face#0_Tstat]' """, argstr='-set %s...', mandatory=True, @@ -932,17 +932,26 @@ class Mema(AFNICommand): >>> from nipype.interfaces import afni >>> mema = afni.Mema() - >>> mema.inputs.sets = ['analysis_name', [[subject_1, s1_betas, s1_t, numb1, numt1], [subject2, s2_bets, s2_ts, numb2, numt2], ...]] - >>> mema.inputs.max_zeros = 4 - >>> mema.inputs.model_outliers = True - >>> mema.inputs.residual_z = True - >>> mema.inputs.out_file = 'Results.BRIK' - - >>> from nipype.interfaces import afni - >>> mema = afni.Mema() - >>> mema.inputs.missing_data['File1', 'File2'] - >>> mema.inputs.sets = ['analysis_name', [[subject_1, s1_betas, s1_ts], [subject2, s2_bets, s2_ts], ...]] - >>> mema.inputs.out_file = 'Results.BRIK' + >>> mema.inputs.sets = [('Placebo', [ + ... ('Jane', 'pb05.Jane.betas.nii', 'pb05.Jane.tvals.nii'), + ... ('John', 'pb05.John.betas.nii', 'pb05.John.tvals.nii'), + ... ('Lisa', 'pb05.Lisa.betas.nii', 'pb05.Lisa.tvals.nii')])] + >>> mema.cmdline + "3dMEMA -equal_variance -no_model_outliers -no_residual_Z -set Placebo Jane \ +pb05.betas.nii pb05.Jane.tvals.nii John pb05.John.betas.nii pb05.John.tvals.nii \ +Lisa pb05.Lisa.betas.nii pb05.Lisa.tvals.nii -verb 1" + + >>> mema.inputs.sets = [('Placebo', [ + ... ('Jane', 'pb05.Jane.Regression+tlrc', 'pb05.Jane.Regression+tlrc', + ... '[face#0_Beta]', '[face#0_Tstat]') + ... ])] + >>> mema.cmdline + "3dMEMA -equal_variance -no_model_outliers -no_residual_Z -set Placebo Jane \ +pb05.Jane.Regression+tlrc'[face#0_Beta]' pb05.Jane.Regression+tlrc'[face#0_Tstat]' -verb 1" + + >>> mema.inputs.missing_data = 0 + "3dMEMA -equal_variance -missing_data 0 -no_model_outliers -no_residual_Z -set Placebo \ +Jane pb05.Jane.Regression+tlrc'[face#0_Beta]' pb05.Jane.Regression+tlrc'[face#0_Tstat]' -verb 1" >>> from nipype.interfaces import afni >>> mema = afni.Mema() @@ -972,7 +981,7 @@ def _format_arg(self, name, trait_spec, value): for setname, setopts in value: formatted_subject = [] for this_set in setopts: - if len(this_set) == 4: + if len(this_set) == 5: subid, beta_file, ttst_file, beta_opts, ttst_opts = this_set if beta_opts: beta_file = "%s'%s'" % (beta_file, beta_opts) @@ -986,11 +995,13 @@ def _format_arg(self, name, trait_spec, value): return super(Mema, self)._format_arg(name, trait_spec, value) - def _parse_inputs(self, skip=None): if skip is None: skip = [] - return super(Mema, self)._parse_inputs(skip) + parsed = super(Mema, self)._parse_inputs(skip) + + # TODO: Check groups + return parsed def _list_outputs(self): outputs = self.output_spec().get() From d7c70cbe04deaeff0262e895e621ed5eafb9a058 Mon Sep 17 00:00:00 2001 From: oesteban Date: Fri, 30 Aug 2019 13:10:00 -0700 Subject: [PATCH 7/7] fix: finishing up 3dMEMA tests --- nipype/conftest.py | 6 + nipype/interfaces/afni/model.py | 206 +++++++++++++++++++++++--------- 2 files changed, 153 insertions(+), 59 deletions(-) diff --git a/nipype/conftest.py b/nipype/conftest.py index 9a9175ce28..360d83cb27 100644 --- a/nipype/conftest.py +++ b/nipype/conftest.py @@ -12,11 +12,17 @@ shutil.copytree(NIPYPE_DATADIR, data_dir) +def _create_testfiles(*args): + for file in args: + open(file, 'w').close() + + @pytest.fixture(autouse=True) def add_np(doctest_namespace): doctest_namespace['np'] = numpy doctest_namespace['os'] = os doctest_namespace["datadir"] = data_dir + doctest_namespace["create_testfiles"] = _create_testfiles @pytest.fixture(autouse=True) diff --git a/nipype/interfaces/afni/model.py b/nipype/interfaces/afni/model.py index 19d41aed46..e41ab247fa 100644 --- a/nipype/interfaces/afni/model.py +++ b/nipype/interfaces/afni/model.py @@ -708,30 +708,21 @@ class MemaInputSpec(AFNICommandInputSpec): # conditionally mandatory arguments groups = traits.List( - ['G1'], Str(minlen=1), - desc='Name of 1 or 2 groups. This option must be used when comparing two groups.', + desc="""\ +Name of 1 or 2 groups. This option must be used when comparing two groups. +Default is one group named 'G1'. The labels here are used to name the sub-bricks +in the output. When there are two groups, the 1st and 2nd labels here are associated +with the 1st and 2nd datasets specified respectively through option -set, and their +group difference is the second group minus the first one, similar to 3dttest but +different from 3dttest++.""", argstr='-groups %s', minlen=1, maxlen=2, ) - """" - -groups GROUP1 [GROUP2]: Name of 1 or 2 groups. This option must be used - when comparing two groups. Default is one group - named 'G1'. The labels here are used to name - the sub-bricks in the output. When there are - two groups, the 1st and 2nd labels here are - associated with the 1st and 2nd datasets - specified respectively through option -set, - and their group difference is the second group - minus the first one, similar to 3dttest but - different from 3dttest++. - """ - equal_variance = traits.Bool( True, - usedefault=True, argstr=['-unequal_variance', '-equal_variance'], xor=['covariates'], desc="""\ @@ -758,6 +749,7 @@ class MemaInputSpec(AFNICommandInputSpec): ' the values of these covariates for each subject. It is recommended to use the' ' covariates file generated by 3dREMLfit.', argstr='-covariates %s', + xor=['equal_variance'], ) covariates_center = traits.Str( @@ -775,7 +767,7 @@ class MemaInputSpec(AFNICommandInputSpec): each of the groups (CEN_K.A, CEN_K.B). Example: If you had covariates age, and weight, you would use: -covariates_center age = 78 55 weight = 165 198""", - argstr='covariates_center %s' + argstr="-covariates_center %s" ) covariates_model = traits.Tuple( @@ -819,7 +811,7 @@ class MemaInputSpec(AFNICommandInputSpec): argstr='-mask %s' ) - max_zeros = traits.Range( # Please revise all the other possible settings\ + max_zeros = traits.Range( desc="""\ Do not compute statistics at any voxel that has \ more than MM zero beta coefficients or GLTs. Voxels around \ @@ -846,25 +838,19 @@ class MemaInputSpec(AFNICommandInputSpec): This option corrects for inflated statistics for the voxels where some subjects do not have any data available due to imperfect spatial alignment or other reasons. The absence of this option -means no missing data will be assumed.""", +means no missing data will be assumed. +0: With this format the zero value at a voxel of each subject + will be interpreted as missing data. +File1 [File2]: Information about missing data is specified + with file of 1 or 2 groups (the number 1 or 2 + and file order should be consistent with those + in option -groups). The voxel value of each file + indicates the number of sujects with missing data + in that group.""", argstr='-missing_data %s', xor=['max_zeros'] ) - # -missing_data: This option corrects for inflated statistics for the voxels where - # some subjects do not have any data available due to imperfect - # spatial alignment or other reasons. The absence of this option - # means no missing data will be assumed. Two formats of option - # setting exist as shown below. - # -missing_data 0: With this format the zero value at a voxel of each subject - # will be interpreted as missing data. - # -missing_data File1 [File2]: Information about missing data is specified - # with file of 1 or 2 groups (the number 1 or 2 - # and file order should be consistent with those - # in option -groups). The voxel value of each file - # indicates the number of sujects with missing data - # in that group. - outliers = traits.Bool( False, usedefault=True, @@ -930,43 +916,133 @@ class Mema(AFNICommand): Examples ======== + .. testsetup:: + >>> create_testfiles(*[ + ... 'pb05.%s.%s.nii' % (n, c) + ... for n in ('Jane', 'John', 'Lisa', 'Amy', 'Josh', 'Mark') + ... for c in ('betas', 'tvals')]) + >>> create_testfiles('pb05.Jane.Regression+tlrc') + >>> create_testfiles('covariates.txt') + >>> from nipype.interfaces import afni >>> mema = afni.Mema() - >>> mema.inputs.sets = [('Placebo', [ - ... ('Jane', 'pb05.Jane.betas.nii', 'pb05.Jane.tvals.nii'), - ... ('John', 'pb05.John.betas.nii', 'pb05.John.tvals.nii'), - ... ('Lisa', 'pb05.Lisa.betas.nii', 'pb05.Lisa.tvals.nii')])] - >>> mema.cmdline - "3dMEMA -equal_variance -no_model_outliers -no_residual_Z -set Placebo Jane \ -pb05.betas.nii pb05.Jane.tvals.nii John pb05.John.betas.nii pb05.John.tvals.nii \ -Lisa pb05.Lisa.betas.nii pb05.Lisa.tvals.nii -verb 1" - >>> mema.inputs.sets = [('Placebo', [ ... ('Jane', 'pb05.Jane.Regression+tlrc', 'pb05.Jane.Regression+tlrc', ... '[face#0_Beta]', '[face#0_Tstat]') ... ])] >>> mema.cmdline - "3dMEMA -equal_variance -no_model_outliers -no_residual_Z -set Placebo Jane \ + "3dMEMA -no_model_outliers -no_residual_Z -set Placebo Jane \ pb05.Jane.Regression+tlrc'[face#0_Beta]' pb05.Jane.Regression+tlrc'[face#0_Tstat]' -verb 1" >>> mema.inputs.missing_data = 0 - "3dMEMA -equal_variance -missing_data 0 -no_model_outliers -no_residual_Z -set Placebo \ + >>> mema.cmdline + "3dMEMA -missing_data 0 -no_model_outliers -no_residual_Z -set Placebo \ Jane pb05.Jane.Regression+tlrc'[face#0_Beta]' pb05.Jane.Regression+tlrc'[face#0_Tstat]' -verb 1" - >>> from nipype.interfaces import afni + >>> mema.inputs.sets = [ + ... ('Placebo', [ + ... ('Jane', 'pb05.Jane.betas.nii', 'pb05.Jane.tvals.nii'), + ... ('John', 'pb05.John.betas.nii', 'pb05.John.tvals.nii'), + ... ('Lisa', 'pb05.Lisa.betas.nii', 'pb05.Lisa.tvals.nii')]), + ... ('Treatment', [ + ... ('Amy', 'pb05.Amy.betas.nii', 'pb05.Amy.tvals.nii'), + ... ('Josh', 'pb05.Josh.betas.nii', 'pb05.Josh.tvals.nii'), + ... ('Mark', 'pb05.Mark.betas.nii', 'pb05.Mark.tvals.nii')])] + >>> mema.cmdline # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ValueError: + ... + + >>> mema.inputs.groups = ['Placebo', 'Treatment'] + >>> mema.cmdline + '3dMEMA -groups Placebo Treatment \ +-missing_data 0 -no_model_outliers -no_residual_Z -set Placebo \ +Jane pb05.Jane.betas.nii pb05.Jane.tvals.nii \ +John pb05.John.betas.nii pb05.John.tvals.nii \ +Lisa pb05.Lisa.betas.nii pb05.Lisa.tvals.nii -set Treatment \ +Amy pb05.Amy.betas.nii pb05.Amy.tvals.nii \ +Josh pb05.Josh.betas.nii pb05.Josh.tvals.nii \ +Mark pb05.Mark.betas.nii pb05.Mark.tvals.nii -verb 1' + + >>> mema = afni.Mema() - >>> mema.inputs.groups = ['group1', 'group2'] - >>> mema.inputs.sets = ['analysis1_name', [[subject_1, s1_betas, s1_ts], [subject2, s2_bets, s2_ts], ...]] - >>> mema.inputs.sets = ['analysis2_name', [[subject_1, s1_betas, s1_ts], [subject2, s2_bets, s2_ts], ...]] - >>> mema.inputs.n_nonzero = 18 - >>> mema.inputs.hktest = True + >>> mema.inputs.sets = [('Placebo', [ + ... ('Jane', 'pb05.Jane.Regression+tlrc', 'pb05.Jane.Regression+tlrc', + ... '[face#0_Beta]', '[face#0_Tstat]') + ... ])] + >>> mema.inputs.nonzeros = 18 + >>> mema.cmdline + "3dMEMA -n_nonzero 18.000000 -no_model_outliers -no_residual_Z \ +-set Placebo Jane pb05.Jane.Regression+tlrc'[face#0_Beta]' \ +pb05.Jane.Regression+tlrc'[face#0_Tstat]' \ +-verb 1" + + >>> mema.inputs.hk_test = True + >>> mema.cmdline + "3dMEMA -HKtest -n_nonzero 18.000000 -no_model_outliers \ +-no_residual_Z -set Placebo Jane pb05.Jane.Regression+tlrc'[face#0_Beta]' \ +pb05.Jane.Regression+tlrc'[face#0_Tstat]' \ +-verb 1" + >>> mema.inputs.outliers = True + >>> mema.cmdline + "3dMEMA -HKtest -n_nonzero 18.000000 -model_outliers \ +-no_residual_Z -set Placebo Jane pb05.Jane.Regression+tlrc'[face#0_Beta]' \ +pb05.Jane.Regression+tlrc'[face#0_Tstat]' \ +-verb 1" + + >>> mema.inputs.residualZ = True + >>> mema.cmdline + "3dMEMA -HKtest -n_nonzero 18.000000 -model_outliers \ +-residual_Z -set Placebo Jane pb05.Jane.Regression+tlrc'[face#0_Beta]' \ +pb05.Jane.Regression+tlrc'[face#0_Tstat]' \ +-verb 1" + >>> mema.inputs.equal_variance = False - >>> mema.inputs.residual_z = True - >>> mema.inputs.covariates = 'CovFile.txt' + >>> mema.cmdline + "3dMEMA -unequal_variance -HKtest -n_nonzero 18.000000 -model_outliers \ +-residual_Z -set Placebo Jane pb05.Jane.Regression+tlrc'[face#0_Beta]' \ +pb05.Jane.Regression+tlrc'[face#0_Tstat]' \ +-verb 1" + + >>> mema = afni.Mema() + >>> mema.inputs.sets = [('Placebo', [ + ... ('Jane', 'pb05.Jane.Regression+tlrc', 'pb05.Jane.Regression+tlrc', + ... '[face#0_Beta]', '[face#0_Tstat]') + ... ])] + >>> mema.inputs.covariates = 'covariates.txt' + >>> mema.cmdline + "3dMEMA -covariates covariates.txt \ +-no_model_outliers -no_residual_Z -set Placebo \ +Jane pb05.Jane.Regression+tlrc'[face#0_Beta]' \ +pb05.Jane.Regression+tlrc'[face#0_Tstat]' \ +-verb 1" + >>> mema.inputs.covariates_center = 'age = 25 13 weight = 100 150' - >>> mema.inputs.covariates_model = ['different', 'same'] + >>> mema.cmdline + "3dMEMA -covariates covariates.txt -covariates_center age = 25 13 \ +weight = 100 150 -no_model_outliers -no_residual_Z -set Placebo \ +Jane pb05.Jane.Regression+tlrc'[face#0_Beta]' \ +pb05.Jane.Regression+tlrc'[face#0_Tstat]' \ +-verb 1" + + >>> mema.inputs.covariates_model = ('different', 'same') + >>> mema.cmdline + "3dMEMA -covariates covariates.txt -covariates_center age = 25 13 \ +weight = 100 150 -covariates_model center=different slope=same \ +-no_model_outliers -no_residual_Z -set Placebo \ +Jane pb05.Jane.Regression+tlrc'[face#0_Beta]' \ +pb05.Jane.Regression+tlrc'[face#0_Tstat]' \ +-verb 1" + >>> mema.inputs.out_file = 'Results.BRIK' + >>> mema.cmdline + "3dMEMA -covariates covariates.txt -covariates_center age = 25 13 \ +weight = 100 150 -covariates_model center=different slope=same \ +-prefix Results.BRIK -no_model_outliers -no_residual_Z -set Placebo \ +Jane pb05.Jane.Regression+tlrc'[face#0_Beta]' \ +pb05.Jane.Regression+tlrc'[face#0_Tstat]' \ +-verb 1" """ @@ -974,9 +1050,12 @@ class Mema(AFNICommand): input_spec = MemaInputSpec output_spec = MemaOutputSpec + def __init__(self, *args, **kwargs): + self._n_sets = None + return super(Mema, self).__init__(*args, **kwargs) + def _format_arg(self, name, trait_spec, value): if name == "sets": - self._n_sets = len(value) formatted_values = [] for setname, setopts in value: formatted_subject = [] @@ -992,16 +1071,25 @@ def _format_arg(self, name, trait_spec, value): formatted_subject.append(' '.join((subid, beta_file, ttst_file))) formatted_values.append(' '.join([setname] + formatted_subject)) value = formatted_values - return super(Mema, self)._format_arg(name, trait_spec, value) def _parse_inputs(self, skip=None): + self._n_sets = len(self.inputs.sets) + if self._n_sets == 2 and not isdefined(self.inputs.groups): + raise ValueError( + 'When two of ``-set`` are given, the option ``-groups`` must be defined.') + elif isdefined(self.inputs.groups) and len(self.inputs.groups) != self._n_sets: + raise ValueError( + 'Number of group names (``-groups``) and sets (``-set``) must match.') + + if (isdefined(self.inputs.missing_data) and isinstance(self.inputs.missing_data, list) and + len(self.inputs.missing_data) != self._n_sets): + raise ValueError( + 'Number of files given in ``-missing_data`` must match the number of sets.') + if skip is None: skip = [] - parsed = super(Mema, self)._parse_inputs(skip) - - # TODO: Check groups - return parsed + return super(Mema, self)._parse_inputs(skip) def _list_outputs(self): outputs = self.output_spec().get()