@@ -1865,6 +1865,262 @@ def _list_outputs(self):
18651865 return outputs
18661866
18671867
1868+ class MultiChannelNewSegmentInputSpec(SPMCommandInputSpec):
1869+ channels = traits.List(
1870+ traits.Tuple(
1871+ InputMultiPath(
1872+ ImageFileSPM(exists=True),
1873+ mandatory=True,
1874+ desc="A list of files to be segmented",
1875+ field="channel",
1876+ copyfile=False,
1877+ ),
1878+ traits.Tuple(
1879+ traits.Float(),
1880+ traits.Float(),
1881+ traits.Tuple(traits.Bool, traits.Bool),
1882+ desc="""A tuple with the following fields:
1883+ - bias reguralisation (0-10)
1884+ - FWHM of Gaussian smoothness of bias
1885+ - which maps to save (Field, Corrected) - a tuple of two boolean values""",
1886+ field="channel",
1887+ )
1888+ ),
1889+ desc="""A list of tuples (one per each channel) with the following fields:
1890+ - a list of channel files (only 1rst channel files will be segmented)
1891+ - a tuple with the following channel-specific info fields:
1892+ - bias reguralisation (0-10)
1893+ - FWHM of Gaussian smoothness of bias
1894+ - which maps to save (Field, Corrected) - a tuple of two boolean values""",
1895+ field="channel",
1896+ )
1897+ tissues = traits.List(
1898+ traits.Tuple(
1899+ traits.Tuple(ImageFileSPM(exists=True), traits.Int()),
1900+ traits.Int(),
1901+ traits.Tuple(traits.Bool, traits.Bool),
1902+ traits.Tuple(traits.Bool, traits.Bool),
1903+ ),
1904+ desc="""A list of tuples (one per tissue) with the following fields:
1905+ - tissue probability map (4D), 1-based index to frame
1906+ - number of gaussians
1907+ - which maps to save [Native, DARTEL] - a tuple of two boolean values
1908+ - which maps to save [Unmodulated, Modulated] - a tuple of two boolean values""",
1909+ field="tissue",
1910+ )
1911+ affine_regularization = traits.Enum(
1912+ "mni",
1913+ "eastern",
1914+ "subj",
1915+ "none",
1916+ field="warp.affreg",
1917+ desc="mni, eastern, subj, none ",
1918+ )
1919+ warping_regularization = traits.Either(
1920+ traits.List(traits.Float(), minlen=5, maxlen=5),
1921+ traits.Float(),
1922+ field="warp.reg",
1923+ desc=(
1924+ "Warping regularization "
1925+ "parameter(s). Accepts float "
1926+ "or list of floats (the "
1927+ "latter is required by "
1928+ "SPM12)"
1929+ ),
1930+ )
1931+ sampling_distance = traits.Float(
1932+ field="warp.samp", desc=("Sampling distance on data for parameter estimation"),
1933+ )
1934+ write_deformation_fields = traits.List(
1935+ traits.Bool(),
1936+ minlen=2,
1937+ maxlen=2,
1938+ field="warp.write",
1939+ desc=("Which deformation fields to write:[Inverse, Forward]"),
1940+ )
1941+
1942+
1943+ class MultiChannelNewSegmentOutputSpec(TraitedSpec):
1944+ native_class_images = traits.List(
1945+ traits.List(File(exists=True)), desc="native space probability maps"
1946+ )
1947+ dartel_input_images = traits.List(
1948+ traits.List(File(exists=True)), desc="dartel imported class images"
1949+ )
1950+ normalized_class_images = traits.List(
1951+ traits.List(File(exists=True)), desc="normalized class images"
1952+ )
1953+ modulated_class_images = traits.List(
1954+ traits.List(File(exists=True)), desc=("modulated+normalized class images")
1955+ )
1956+ transformation_mat = OutputMultiPath(
1957+ File(exists=True), desc="Normalization transformation"
1958+ )
1959+ bias_corrected_images = OutputMultiPath(
1960+ File(exists=True), desc="bias corrected images"
1961+ )
1962+ bias_field_images = OutputMultiPath(File(exists=True), desc="bias field images")
1963+ forward_deformation_field = OutputMultiPath(File(exists=True))
1964+ inverse_deformation_field = OutputMultiPath(File(exists=True))
1965+
1966+
1967+ class MultiChannelNewSegment(SPMCommand):
1968+ """Use spm_preproc8 (New Segment) to separate structural images into
1969+ different tissue classes. Supports multiple modalities and multichannel inputs.
1970+
1971+ http://www.fil.ion.ucl.ac.uk/spm/doc/manual.pdf#page=45
1972+
1973+ Examples
1974+ --------
1975+ >>> import nipype.interfaces.spm as spm
1976+ >>> seg = spm.MultiChannelNewSegment()
1977+ >>> seg.inputs.channels = [('structural.nii',(0.0001, 60, (True, True)))]
1978+ >>> seg.run() # doctest: +SKIP
1979+
1980+ For VBM pre-processing [http://www.fil.ion.ucl.ac.uk/~john/misc/VBMclass10.pdf],
1981+ TPM.nii should be replaced by /path/to/spm8/toolbox/Seg/TPM.nii
1982+
1983+ >>> seg = MultiChannelNewSegment()
1984+ >>> channel1= ('T1.nii',(0.0001, 60, (True, True)))
1985+ >>> channel2= ('T2.nii',(0.0001, 60, (True, True)))
1986+ >>> seg.inputs.channels = [channel1, channel2]
1987+ >>> tissue1 = (('TPM.nii', 1), 2, (True,True), (False, False))
1988+ >>> tissue2 = (('TPM.nii', 2), 2, (True,True), (False, False))
1989+ >>> tissue3 = (('TPM.nii', 3), 2, (True,False), (False, False))
1990+ >>> tissue4 = (('TPM.nii', 4), 2, (False,False), (False, False))
1991+ >>> tissue5 = (('TPM.nii', 5), 2, (False,False), (False, False))
1992+ >>> seg.inputs.tissues = [tissue1, tissue2, tissue3, tissue4, tissue5]
1993+ >>> seg.run() # doctest: +SKIP
1994+
1995+ """
1996+
1997+ input_spec = MultiChannelNewSegmentInputSpec
1998+ output_spec = MultiChannelNewSegmentOutputSpec
1999+
2000+ def __init__(self, **inputs):
2001+ _local_version = SPMCommand().version
2002+ if _local_version and "12." in _local_version:
2003+ self._jobtype = "spatial"
2004+ self._jobname = "preproc"
2005+ else:
2006+ self._jobtype = "tools"
2007+ self._jobname = "preproc8"
2008+
2009+ SPMCommand.__init__(self, **inputs)
2010+
2011+ def _format_arg(self, opt, spec, val):
2012+ """Convert input to appropriate format for spm
2013+ """
2014+
2015+ if opt == "channels":
2016+ # structure have to be recreated because of some weird traits error
2017+ new_channels = []
2018+ for channel in val:
2019+ new_channel = {}
2020+ new_channel["vols"] = scans_for_fnames(channel[0])
2021+ if isdefined(channel[1]):
2022+ info = channel[1]
2023+ new_channel["biasreg"] = info[0]
2024+ new_channel["biasfwhm"] = info[1]
2025+ new_channel["write"] = [int(info[2][0]), int(info[2][1])]
2026+ new_channels.append(new_channel)
2027+ return new_channels
2028+ elif opt == "tissues":
2029+ new_tissues = []
2030+ for tissue in val:
2031+ new_tissue = {}
2032+ new_tissue["tpm"] = np.array(
2033+ [",".join([tissue[0][0], str(tissue[0][1])])], dtype=object
2034+ )
2035+ new_tissue["ngaus"] = tissue[1]
2036+ new_tissue["native"] = [int(tissue[2][0]), int(tissue[2][1])]
2037+ new_tissue["warped"] = [int(tissue[3][0]), int(tissue[3][1])]
2038+ new_tissues.append(new_tissue)
2039+ return new_tissues
2040+ elif opt == "write_deformation_fields":
2041+ return super(MultiChannelNewSegment, self)._format_arg(
2042+ opt, spec, [int(val[0]), int(val[1])]
2043+ )
2044+ else:
2045+ return super(MultiChannelNewSegment, self)._format_arg(opt, spec, val)
2046+
2047+ def _list_outputs(self):
2048+ outputs = self._outputs().get()
2049+ outputs["native_class_images"] = []
2050+ outputs["dartel_input_images"] = []
2051+ outputs["normalized_class_images"] = []
2052+ outputs["modulated_class_images"] = []
2053+ outputs["transformation_mat"] = []
2054+ outputs["bias_corrected_images"] = []
2055+ outputs["bias_field_images"] = []
2056+ outputs["inverse_deformation_field"] = []
2057+ outputs["forward_deformation_field"] = []
2058+
2059+ n_classes = 5
2060+ if isdefined(self.inputs.tissues):
2061+ n_classes = len(self.inputs.tissues)
2062+ for i in range(n_classes):
2063+ outputs["native_class_images"].append([])
2064+ outputs["dartel_input_images"].append([])
2065+ outputs["normalized_class_images"].append([])
2066+ outputs["modulated_class_images"].append([])
2067+
2068+ # main outputs are generated for the first channel images only
2069+ for filename in self.inputs.channels[0][0]:
2070+ pth, base, ext = split_filename(filename)
2071+ if isdefined(self.inputs.tissues):
2072+ for i, tissue in enumerate(self.inputs.tissues):
2073+ if tissue[2][0]:
2074+ outputs["native_class_images"][i].append(
2075+ os.path.join(pth, "c%d%s.nii" % (i + 1, base))
2076+ )
2077+ if tissue[2][1]:
2078+ outputs["dartel_input_images"][i].append(
2079+ os.path.join(pth, "rc%d%s.nii" % (i + 1, base))
2080+ )
2081+ if tissue[3][0]:
2082+ outputs["normalized_class_images"][i].append(
2083+ os.path.join(pth, "wc%d%s.nii" % (i + 1, base))
2084+ )
2085+ if tissue[3][1]:
2086+ outputs["modulated_class_images"][i].append(
2087+ os.path.join(pth, "mwc%d%s.nii" % (i + 1, base))
2088+ )
2089+ else:
2090+ for i in range(n_classes):
2091+ outputs["native_class_images"][i].append(
2092+ os.path.join(pth, "c%d%s.nii" % (i + 1, base))
2093+ )
2094+ outputs["transformation_mat"].append(
2095+ os.path.join(pth, "%s_seg8.mat" % base)
2096+ )
2097+
2098+ if isdefined(self.inputs.write_deformation_fields):
2099+ if self.inputs.write_deformation_fields[0]:
2100+ outputs["inverse_deformation_field"].append(
2101+ os.path.join(pth, "iy_%s.nii" % base)
2102+ )
2103+ if self.inputs.write_deformation_fields[1]:
2104+ outputs["forward_deformation_field"].append(
2105+ os.path.join(pth, "y_%s.nii" % base)
2106+ )
2107+
2108+ # bias field related images are generated for images in all channels
2109+ for channel in self.inputs.channels:
2110+ for filename in channel[0]:
2111+ pth, base, ext = split_filename(filename)
2112+ if isdefined(channel[1]):
2113+ if channel[1][2][0]:
2114+ outputs["bias_field_images"].append(
2115+ os.path.join(pth, "BiasField_%s.nii" % (base))
2116+ )
2117+ if channel[1][2][1]:
2118+ outputs["bias_corrected_images"].append(
2119+ os.path.join(pth, "m%s.nii" % (base))
2120+ )
2121+ return outputs
2122+
2123+
18682124class SmoothInputSpec(SPMCommandInputSpec):
18692125 in_files = InputMultiPath(
18702126 ImageFileSPM(exists=True),
0 commit comments