|
| 1 | +import shutil |
| 2 | +from pathlib import Path |
| 3 | + |
| 4 | +from nipype.interfaces.base import ( |
| 5 | + CommandLine, |
| 6 | + CommandLineInputSpec, |
| 7 | + Directory, |
| 8 | + File, |
| 9 | + TraitedSpec, |
| 10 | + traits, |
| 11 | +) |
| 12 | + |
| 13 | + |
| 14 | +class MCRIBReconAllInputSpec(CommandLineInputSpec): |
| 15 | + # Input structure massaging |
| 16 | + outdir = Directory( |
| 17 | + exists=True, |
| 18 | + hash_files=False, |
| 19 | + desc='Path to save output, or path of existing MCRIBS output', |
| 20 | + ) |
| 21 | + subjects_dir = Directory( |
| 22 | + exists=True, |
| 23 | + hash_files=False, |
| 24 | + desc='Path to FreeSurfer subjects directory', |
| 25 | + ) |
| 26 | + subject_id = traits.Str( |
| 27 | + required=True, |
| 28 | + argstr='%s', |
| 29 | + position=-1, |
| 30 | + desc='Subject ID', |
| 31 | + ) |
| 32 | + t1w_file = File( |
| 33 | + exists=True, |
| 34 | + copyfile=True, |
| 35 | + desc='T1w to be used for deformable (must be registered to T2w image)', |
| 36 | + ) |
| 37 | + t2w_file = File( |
| 38 | + exists=True, |
| 39 | + required=True, |
| 40 | + copyfile=True, |
| 41 | + desc='T2w (Isotropic + N4 corrected)', |
| 42 | + ) |
| 43 | + segmentation_file = File( |
| 44 | + desc='Segmentation file (skips tissue segmentation)', |
| 45 | + ) |
| 46 | + |
| 47 | + # MCRIBS options |
| 48 | + conform = traits.Bool( |
| 49 | + argstr='--conform', |
| 50 | + desc='Reorients to radiological, axial slice orientation. Resamples to isotropic voxels', |
| 51 | + ) |
| 52 | + tissueseg = traits.Bool( |
| 53 | + argstr='--tissueseg', |
| 54 | + desc='Perform tissue type segmentation', |
| 55 | + ) |
| 56 | + surfrecon = traits.Bool( |
| 57 | + True, |
| 58 | + usedefault=True, |
| 59 | + argstr='--surfrecon', |
| 60 | + desc='Reconstruct surfaces', |
| 61 | + ) |
| 62 | + surfrecon_method = traits.Enum( |
| 63 | + 'Deformable', |
| 64 | + argstr='--surfreconmethod %s', |
| 65 | + usedefault=True, |
| 66 | + desc='Surface reconstruction method', |
| 67 | + ) |
| 68 | + join_thresh = traits.Float( |
| 69 | + 1.0, |
| 70 | + argstr='--deformablejointhresh %f', |
| 71 | + usedefault=True, |
| 72 | + desc='Join threshold parameter for Deformable', |
| 73 | + ) |
| 74 | + fast_collision = traits.Bool( |
| 75 | + True, |
| 76 | + argstr='--deformablefastcollision', |
| 77 | + usedefault=True, |
| 78 | + desc='Use Deformable fast collision test', |
| 79 | + ) |
| 80 | + autorecon_after_surf = traits.Bool( |
| 81 | + True, |
| 82 | + argstr='--autoreconaftersurf', |
| 83 | + usedefault=True, |
| 84 | + desc='Do all steps after surface reconstruction', |
| 85 | + ) |
| 86 | + segstats = traits.Bool( |
| 87 | + True, |
| 88 | + argstr='--segstats', |
| 89 | + usedefault=True, |
| 90 | + desc='Compute statistics on segmented volumes', |
| 91 | + ) |
| 92 | + nthreads = traits.Int( |
| 93 | + argstr='-nthreads %d', |
| 94 | + desc='Number of threads for multithreading applications', |
| 95 | + ) |
| 96 | + |
| 97 | + |
| 98 | +class MCRIBReconAllOutputSpec(TraitedSpec): |
| 99 | + mcribs_dir = Directory(desc='MCRIBS output directory') |
| 100 | + |
| 101 | + |
| 102 | +class MCRIBReconAll(CommandLine): |
| 103 | + _cmd = 'MCRIBReconAll' |
| 104 | + input_spec = MCRIBReconAllInputSpec |
| 105 | + output_spec = MCRIBReconAllOutputSpec |
| 106 | + _no_run = False |
| 107 | + |
| 108 | + @property |
| 109 | + def cmdline(self): |
| 110 | + cmd = super().cmdline |
| 111 | + # Avoid processing if valid |
| 112 | + if self.inputs.outdir: |
| 113 | + sid = self.inputs.subject_id |
| 114 | + logf = Path(self.inputs.outdir) / sid / 'logs' / f'{sid}.log' |
| 115 | + if logf.exists(): |
| 116 | + logtxt = logf.read_text().splitlines()[-3:] |
| 117 | + self._no_run = 'Finished without error' in logtxt |
| 118 | + if self._no_run: |
| 119 | + return "echo MCRIBSReconAll: nothing to do" |
| 120 | + return cmd |
| 121 | + |
| 122 | + def _setup_directory_structure(self, mcribs_dir: Path) -> None: |
| 123 | + ''' |
| 124 | + Create the required structure for skipping steps. |
| 125 | +
|
| 126 | + The directory tree |
| 127 | + ------------------ |
| 128 | +
|
| 129 | + <subject_id>/ |
| 130 | + ├── RawT2 |
| 131 | + │ └── <subject_id>.nii.gz |
| 132 | + ├── SurfReconDeformable |
| 133 | + │ └── <subject_id> |
| 134 | + │ └── temp |
| 135 | + │ └── t2w-image.nii.gz |
| 136 | + ├── TissueSeg |
| 137 | + │ ├── <subject_id>_all_labels.nii.gz |
| 138 | + │ └── <subject_id>_all_labels_manedit.nii.gz |
| 139 | + └── TissueSegDrawEM |
| 140 | + └── <subject_id> |
| 141 | + └── N4 |
| 142 | + └── <subject_id>.nii.gz |
| 143 | + ''' |
| 144 | + sid = self.inputs.subject_id |
| 145 | + mkdir_kw = {'parents': True, 'exist_ok': True} |
| 146 | + root = mcribs_dir / sid |
| 147 | + root.mkdir(**mkdir_kw) |
| 148 | + |
| 149 | + # T2w operations |
| 150 | + t2w = root / 'RawT2' / f'{sid}.nii.gz' |
| 151 | + t2w.parent.mkdir(**mkdir_kw) |
| 152 | + if not t2w.exists(): |
| 153 | + shutil.copy(self.inputs.t2w_file, str(t2w)) |
| 154 | + |
| 155 | + if not self.inputs.conform: |
| 156 | + t2wiso = root / 'RawT2RadiologicalIsotropic' / f'{sid}.nii.gz' |
| 157 | + t2wiso.parent.mkdir(**mkdir_kw) |
| 158 | + if not t2wiso.exists(): |
| 159 | + t2wiso.symlink_to(f'../RawT2/{sid}.nii.gz') |
| 160 | + |
| 161 | + n4 = root / 'TissueSegDrawEM' / sid / 'N4' / f'{sid}.nii.gz' |
| 162 | + n4.parent.mkdir(**mkdir_kw) |
| 163 | + if not n4.exists(): |
| 164 | + n4.symlink_to(f'../../../RawT2/{sid}.nii.gz') |
| 165 | + |
| 166 | + # Segmentation |
| 167 | + if self.inputs.segmentation_file: |
| 168 | + # TissueSeg directive disabled |
| 169 | + tisseg = root / 'TissueSeg' / f'{sid}_all_labels.nii.gz' |
| 170 | + tisseg.parent.mkdir(**mkdir_kw) |
| 171 | + if not tisseg.exists(): |
| 172 | + shutil.copy(self.inputs.segmentation_file, str(tisseg)) |
| 173 | + manedit = tisseg.parent / f'{sid}_all_labels_manedit.nii.gz' |
| 174 | + if not manedit.exists(): |
| 175 | + manedit.symlink_to(tisseg.name) |
| 176 | + |
| 177 | + if self.inputs.surfrecon: |
| 178 | + t2wseg = root / 'TissueSeg' / f'{sid}_t2w_restore.nii.gz' |
| 179 | + if not t2wseg.exists(): |
| 180 | + t2wseg.symlink_to(f'../RawT2/{sid}.nii.gz') |
| 181 | + |
| 182 | + surfrec = root / 'SurfReconDeformable' / sid / 'temp' / 't2w-image.nii.gz' |
| 183 | + surfrec.parent.mkdir(**mkdir_kw) |
| 184 | + if not surfrec.exists(): |
| 185 | + surfrec.symlink_to(f'../../../RawT2/{sid}.nii.gz') |
| 186 | + # TODO?: T1w -> <subject_id>/RawT1RadiologicalIsotropic/<subjectid>.nii.gz |
| 187 | + return |
| 188 | + |
| 189 | + def _run_interface(self, runtime): |
| 190 | + # if users wish to preserve their runs |
| 191 | + mcribs_dir = self.inputs.outdir or Path(runtime.cwd) / 'mcribs' |
| 192 | + self._mcribs_dir = Path(mcribs_dir) |
| 193 | + self._setup_directory_structure(self._mcribs_dir) |
| 194 | + # overwrite CWD to be in MCRIB subject's directory |
| 195 | + runtime.cwd = str(self._mcribs_dir / self.inputs.subject_id) |
| 196 | + return super()._run_interface(runtime) |
| 197 | + |
| 198 | + def _list_outputs(self): |
| 199 | + outputs = self._outputs().get() |
| 200 | + outputs['mcribs_dir'] = str(self._mcribs_dir) |
| 201 | + |
| 202 | + # Copy freesurfer directory into FS subjects dir |
| 203 | + sid = self.inputs.subject_id |
| 204 | + mcribs_fs = self._mcribs_dir / sid / 'freesurfer' / sid |
| 205 | + if mcribs_fs.exists() and self.inputs.subjects_dir: |
| 206 | + dst = Path(self.inputs.subjects_dir) / self.inputs.subject_id |
| 207 | + if not dst.exists(): |
| 208 | + shutil.copytree(mcribs_fs, dst) |
| 209 | + |
| 210 | + return outputs |
0 commit comments