def deface(input_files, outdir, reference_file=None, verbose=0, fs_config=DEFAULT_FREESURFER_PATH): """ Deface MRI head images using the FreeSurfer 'mri_deface' command. Parameters ---------- input_files: list of str Input MRI head images to be defaced. outdir: str The output folder. reference_file: str (optional, default None) The image that must be used as reference if more than one image have been supplied as input. verbose: int (optional, default 0) The verbosity level. fs_config: str (optional, default DEFAULT_FREESURFER_PATH) The FreeSurfer configuration file. Returns ------- deface_files: list of str The defaced input MRI head images. snap_files: list of str The corresponding snaps that can be used to check the defacing result. """ # Check input parameters outdir = os.path.abspath(outdir) if len(input_files) == 0: raise ValueError("You must specify at least one image.") elif len(input_files) > 1: raise ValueError("Mutliple input files not yet supported.") # if reference_file is None or reference_file not in input_files: # raise ValueError("If more than one image is specified, you must " # "also specify one of them as reference. The " # "reference image will be used for spatial " # "co-registration with an atlas, and others " # "will use the reference facial mask.") # Define the command deface_file = os.path.join(outdir, os.path.basename(input_files[0])) wrapper = FSWrapper([], shfile=fs_config) face_file = os.path.join(wrapper.environment["FREESURFER_HOME"], "average", "face.gca") skull_file = os.path.join(wrapper.environment["FREESURFER_HOME"], "average", "talairach_mixed_with_skull.gca") cmd = ["mri_deface", input_files[0], skull_file, face_file, deface_file] # Call defacing wrapper = FSWrapper(cmd, shfile=fs_config) wrapper() return [deface_file], None
def mri_binarize(inputfile, outputfile, match=None, wm=False, fsconfig=DEFAULT_FREESURFER_PATH): """ Binarize a FreeSurfer label map. Binding over the FreeSurfer's 'mri_binarize' command. Parameters ---------- inputfile: str (mandatory) input volume. outputfile: str (mandatory) output volume. match: list on int (optional) match labels instead of threshold. wm: bool (optional) set match vals to 2 and 41 (aseg for cerebral WM). fsconfig: str (optional) The freesurfer configuration batch. """ # Call FreeSurfer cmd = ["mri_binarize", "--i", inputfile, "--o", outputfile] if match is not None: cmd.append("--match") cmd.extend(match) if wm: cmd.append("--wm") recon = FSWrapper(cmd, shfile=fsconfig) recon()
def asegstats2table(fsdir, outdir, fsconfig=DEFAULT_FREESURFER_PATH): """ Generate text/ascii tables of freesurfer parcellation stats data 'aseg.stats'. This can then be easily imported into a spreadsheet and/or stats program. Binding over the FreeSurfer's 'asegstats2table' command. Parameters ---------- fsdir: str (mandatory) The freesurfer working directory with all the subjects. outdir: str (mandatory) The statistical destination folder. fsconfig: str (optional) The freesurfer configuration batch. Return ------ statfiles: list of str The freesurfer summary stats. """ # Check input parameters for path in (fsdir, outdir): if not os.path.isdir(path): raise ValueError("'{0}' is not a valid directory.".format(path)) # Parameter that will contain the output stats statfiles = [] # Fist find all the subjects with a stat dir statdirs = glob.glob(os.path.join(fsdir, "*", "stats")) subjects = [item.lstrip(os.sep).split("/")[-2] for item in statdirs] # Save the FreeSurfer current working directory and set the new one fscwd = None if "SUBJECTS_DIR" in os.environ: fscwd = os.environ["SUBJECTS_DIR"] os.environ["SUBJECTS_DIR"] = fsdir # Create the output stat directory fsoutdir = os.path.join(outdir, "stats") if not os.path.isdir(fsoutdir): os.mkdir(fsoutdir) # Call freesurfer statfile = os.path.join(fsoutdir, "aseg_stats_volume.csv") statfiles.append(statfile) cmd = ["asegstats2table", "--subjects"] + subjects + [ "--meas", "volume", "--tablefile", statfile, "--delimiter", "comma" ] recon = FSWrapper(cmd, shfile=fsconfig) recon() # Restore the FreeSurfer working directory if fscwd is not None: os.environ["SUBJECTS_DIR"] = fscwd return statfiles
def mri_surf2surf(hemi, input_surface_file, output_surface_file, ico_order, fsdir, sid, fsconfig=DEFAULT_FREESURFER_PATH): """ Resample surface vertices. Binding over the FreeSurfer's 'mri_surf2surf' command. Parameters ---------- hemi: str (mandatory) hemisphere ('lh' or 'rh'). input_surface_file: str (mandatory) input surface path. output_surface_file: str (mandatory) output surface path. ico_order: int (mandatory) icosahedron order in [0, 7] that will be used to generate the cortical surface texture at a specific tessalation (the corresponding cortical surface can be resampled using the 'clindmri.segmentation.freesurfer.resample_cortical_surface' function). fsdir: str (mandatory) FreeSurfer subjects directory 'SUBJECTS_DIR'. sid: str (mandatory) FreeSurfer subject identifier. fsconfig: str (optional) The FreeSurfer '.sh' config file. """ # Check input parameters for path in (input_surface_file, ): if not os.path.isfile(path): raise ValueError("'{0}' is not a valid input file.".format(path)) for path in (fsdir, ): if not os.path.isdir(path): raise ValueError("'{0}' is not a valid directory.".format(path)) if hemi not in ["lh", "rh"]: raise ValueError("'{0}' is not a valid hemisphere value which must be " "in ['lh', 'rh']".format(hemi)) if ico_order < 0 or ico_order > 7: raise ValueError("'Ico order '{0}' is not in 0-7 " "range.".format(ico_order)) # Define FreeSurfer command cmd = [ "mri_surf2surf", "--hemi", hemi, "--srcsurfval", input_surface_file, "--srcsubject", sid, "--trgsubject", "ico", "--trgicoorder", str(ico_order), "--trgsurfval", output_surface_file, "--sd", fsdir, "--trg_type", "mgz" ] # Execute the FreeSurfer command recon = FSWrapper(cmd, shfile=fsconfig) recon()
def midgray_surface(hemi, outdir, fsdir, sid, fsconfig=DEFAULT_FREESURFER_PATH): """ Create a mid-thickness gray surface. Binding over the FreeSurfer's 'mris_expand' command. Parameters ---------- hemi: str (mandatory) hemisphere ('lh' or 'rh'). outdir: str (mandatory) the destination folder. fsdir: str (mandatory) FreeSurfer subjects directory 'SUBJECTS_DIR'. sid: str (mandatory) FreeSurfer subject identifier. fsconfig: str (optional) The FreeSurfer '.sh' config file. Returns ------- midgray_file: str the mid-thickness gray surface. """ # Check input parameters white_file = os.path.join(fsdir, sid, "surf", "{0}.white".format(hemi)) for path in (white_file, ): if not os.path.isfile(path): raise ValueError("'{0}' is not a valid input file.".format(path)) for path in (fsdir, ): if not os.path.isdir(path): raise ValueError("'{0}' is not a valid directory.".format(path)) if hemi not in ["lh", "rh"]: raise ValueError("'{0}' is not a valid hemisphere value which must be " "in ['lh', 'rh']".format(hemi)) # Define FreeSurfer command midgray_file = os.path.join(outdir, "{0}.graymid".format(hemi)) cmd = ["mris_expand", "-thickness", white_file, "0.5", midgray_file] # Execute the FreeSurfer command recon = FSWrapper(cmd, shfile=fsconfig) recon() # Create a symlink to the 'surf' FreeSurfer subject folder surf_file = os.path.join(fsdir, sid, "surf", "{0}.graymid".format(hemi)) if not os.path.islink(surf_file): os.symlink(midgray_file, surf_file) return midgray_file
def tkregister_translation(mgzfile, fsconfig=DEFAULT_FREESURFER_PATH): """ Get the tkregister translation. FreeSurfer use a special origin for the Right-Anterior-Superior (anatomical coordinates) space. To get the standard, freesurfer scanner space in RAS coordinates we can use the 'mri_info --vox2ras aseg.mgz' or 'mri_info --vox2ras-trk aseg.mgz' commands respectively. Binding over the FreeSurfer's 'mri_info' command. Parameters ---------- mgzfile: str (mandatory) a FreeSurfer '.mgz' file. fsconfig: str (mandatory) the freesurfer configuration file. Returns ------- translation: array the translation matrix between the ras and ras-tkregister spaces. """ # Check the input parameter if not os.path.isfile(mgzfile): raise ValueError("'{0}' is not a valid '.mgz' file.".format(mgzfile)) # Get the affine matrices corresponding to the the ras or ras-tkregister # spaces affines = {} for tkregister in [True, False]: # Execute the FreeSurfer command command = ["mri_info", "--vox2ras", mgzfile] if tkregister: command[1] = "--vox2ras-tkr" process = FSWrapper(command, shfile=fsconfig) process() # Get the affine matrix displayed in the stdout affine = process.stdout.splitlines() affine = ",".join([line.strip() for line in affine]) affine = re.sub(r" *", ",", affine) affine = numpy.fromstring(affine, dtype=float, sep=",").reshape(4, 4) affines[tkregister] = affine # Compute the translation translation = numpy.eye(4) translation += (affines[False] - affines[True]) return translation
def run_freesurfer_cmd(cmd, subjects_dir=None, fsl_init="/etc/fsl/5.0/fsl.sh"): """ To avoid repeating the code to run Freesurfer and check exitcode everywhere. Step: - add $SUBJECTS_DIR to the environment if requested - add FSL's environment if requested (some Freesurfer commands require FSL) - run the Freesurfer cmd - check exit code Parameters ---------- cmd: list of str the command to run (subprocess like). subjects_dir: str, default None. To set the $SUBJECTS_DIR environment variable. add_fsl_env: bool, default False To activate the FSL environment, required for commands like bbregister. fsl_init: str Path to the Bash script setting the FSL environment, if needed. """ fs_process = FSWrapper(cmd) # Add FSL and current env to Freesurfer environment fsl_env = FSLWrapper([], env=os.environ, shfile=fsl_init).environment complete_env = concat_environment(fsl_env, fs_process.environment) fs_process.environment = complete_env if subjects_dir is not None: fs_process.environment["SUBJECTS_DIR"] = subjects_dir fs_process() # Run if fs_process.exitcode != 0: raise FreeSurferRuntimeError(cmd[0], " ".join(cmd[1:])) return fs_process
def mri_binarize(inputfile, outputfile, match=None, wm=False, ventricles=False, inv=False, fsconfig=DEFAULT_FREESURFER_PATH): """ Binarize a FreeSurfer label map. Binding over the FreeSurfer's 'mri_binarize' command. Parameters ---------- inputfile: str (mandatory) input volume. outputfile: str (mandatory) output volume. match: list on int (optional) match labels instead of threshold. wm: bool (optional) set match vals to 2 and 41 (aseg for cerebral WM). inv: bool (optional) inverse the result. fsconfig: str (optional) The freesurfer configuration batch. """ # Check input parameters for path in (inputfile, ): if not os.path.isfile(path): raise ValueError("'{0}' is not a valid file.".format(path)) # Call FreeSurfer cmd = ["mri_binarize", "--i", inputfile, "--o", outputfile] if match is not None: cmd.append("--match") cmd.extend(match) if wm: cmd.append("--wm") if ventricles: cmd.append("--ventricles") if inv: cmd.append("--inv") recon = FSWrapper(cmd, shfile=fsconfig) recon()
def test_normal_execution(self, mock_path, mock_open, mock_warn): """ Test the normal behaviour of the function. """ # Set the mocked functions returned values mock_path.isfile.side_effect = [True] mock_context_manager = mock.Mock() mock_open.return_value = mock_context_manager mock_file = mock.Mock() mock_file.read.return_value = ( "freesurfer-Linux-centos4_x86_64-stable-pub-v5.2.0") mock_enter = mock.Mock() mock_enter.return_value = mock_file mock_exit = mock.Mock() setattr(mock_context_manager, "__enter__", mock_enter) setattr(mock_context_manager, "__exit__", mock_exit) # Test execution os.environ["FREESURFER_HOME"] = "/my/path/mock_fshome" process = FSWrapper(**self.kwargs) self.assertEqual(len(mock_warn.call_args_list), 1)
def test_noreleaseerror_raise(self, mock_path, mock_open, mock_error, mock_env): """ No FreeSurfer release found -> raise ValueError. """ # Set the mocked functions returned values mock_path.isfile.side_effect = [True] mock_context_manager = mock.Mock() mock_open.return_value = mock_context_manager mock_file = mock.Mock() mock_file.read.return_value = "WRONG" mock_enter = mock.Mock() mock_enter.return_value = mock_file mock_exit = mock.Mock() setattr(mock_context_manager, "__enter__", mock_enter) setattr(mock_context_manager, "__exit__", mock_exit) mock_env.return_value = {"FREESURFER_HOME": "/my/path/mock_fshome"} # Test execution process = FSWrapper(**self.kwargs) self.assertEqual(process.environment, mock_env.return_value) self.assertEqual(len(mock_error.call_args_list), 1)
def recon_all_longitudinal(outdir, subject_id, subjects_dirs, timepoints=None, fsconfig=DEFAULT_FREESURFER_PATH): """ Assuming you have run recon-all for all timepoints of a given subject, and that the results are stored in one SUBJECTS_DIR per timepoint, this function will: - create a template for the subject and process it with recon-all - rerun recon-all for all timepoints of the subject using the template Parameters ---------- outdir: str Directory where to output. Created if not already existing. subject_id: str Identifier of subject, used for all timepoints. subjects_dirs: list of str The FreeSurfer SUBJECTS_DIRs of timepoints. timepoints: list of str, default None The timepoint names in the same order as the SUBJECTS_DIRs. Used to create the subject longitudinal IDs. By default timepoints are "1", "2"... fsconfig: str, default <pyfreesurfer.DEFAULT_FREESURFER_PATH> The FreeSurfer configuration batch. Return ------ subject_template_id: str ID of the subject template. subject_long_ids: list of str Longitudinal IDs of the subject for all the timepoints. """ # Check existence of FreeSurfer subject directories for subjects_dir in subjects_dirs: subject_dir = os.path.join(subjects_dir, subject_id) if not os.path.isdir(subject_dir): raise ValueError("Directory does not exist: %s" % subject_dir) # If 'timepoints' not passed, used defaults, else check validity if timepoints is None: timepoints = [str(n) for n in range(1, len(subjects_dirs) + 1)] elif len(timepoints) != len(subjects_dirs): raise ValueError("There should be as many timepoints as subjects_dirs") # If <outdir> does not exist, create it if not os.path.isdir(outdir): os.mkdir(outdir) # FreeSurfer requires a unique SUBJECTS_DIR will all the timepoints to # compute the template: create symbolic links in <outdir> to all timepoints subject_tp_ids = [] # To accumulate all the timepoint IDs for tp, subjects_dir in zip(timepoints, subjects_dirs): subject_tp_id = "%s_%s" % (subject_id, tp) # subject timepoint ID src_path = os.path.join(subjects_dir, subject_id) dst_path = os.path.join(outdir, subject_tp_id) os.symlink(src_path, dst_path) subject_tp_ids.append(subject_tp_id) # STEP 1 - create and process template subject_template_id = "%s_template_%s" % (subject_id, "_".join(timepoints)) cmd = ["recon-all", "-base", subject_template_id] for subj_tp_id in subject_tp_ids: cmd += ["-tp", subj_tp_id] cmd += ["-all"] FSWrapper(cmd, shfile=fsconfig, subjects_dir=outdir)() # STEP 2 - rerun recon-all for all timepoints using the template subject_long_ids = [] for subj_tp_id in subject_tp_ids: cmd = ["recon-all", "-long", subj_tp_id, subject_template_id, "-all"] FSWrapper(cmd, shfile=fsconfig, subjects_dir=outdir)() subject_long_ids += ["%s.long.%s" % (subj_tp_id, subject_template_id)] return subject_template_id, subject_long_ids
def mkstat_sess( sessid, outdir, fsdir, analysis_names, svres=False, svres_unwhitened=False, run_wise=False, max_threads=False, overwrite=False, fsconfig=DEFAULT_FREESURFER_PATH, verbose=0): """ First-Level GLM Analysis Binding around the FreeSurfer's 'selxavg3-sess ' command. Requires matlab or octave. Parameters ---------- sessid: str (mandatory) the session id file or string. outdir: str (mandatory) the folder where the organized FreeSurfer data are. fsdir: str (mandatory) the FreeSurfer working home directory. analysis_names: list of str (mandatory) the configured model names. svres: bool (optional, default False) save residuals (usually not needed). svres_unwhitened: bool (optional, default False) save unwhitened residuals (usually not needed). run_wise: bool (optional, default False) analyze each run separately. max_threads: bool (optional, default False) use all CPUs. overwrite: bool (optional, default False) delete analysis if session of already analyzed. fsconfig: str (optional, default DEFAULT_FREESURFER_PATH) the FreeSurfer configuration batch. verbose: int (optional, default 0) the verbosity level. """ # Change directory to project directory pwd = os.getcwd() os.chdir(outdir) # Call FreeSurfer fMRI preproc cmd = ["selxavg3-sess", "-d", outdir, "-debug", "-no-preproc"] if os.path.isfile(sessid): cmd += ["-sf", sessid] else: cmd += ["-s", sessid] for value, name in ((svres, "-svres"), (svres_unwhitened, "-svres-unwhitened"), (run_wise, "-run-wise"), (overwrite, "-overwrite"), (max_threads, "-max-threads")): if value: cmd.append(name) log_dir = os.path.join(outdir, "logs") if not os.path.isdir(log_dir): os.mkdir(log_dir) cmd += ["-log", os.path.join(log_dir, "selxavg3-sess.log")] cmd += ["-analysis", ""] for name in analysis_names: if verbose > 0: print("[info] Performing analysis '{0}'...".format(name)) cmd[-1] = name wrap = FSWrapper(cmd, shfile=fsconfig, env=os.environ, subjects_dir=fsdir) wrap() # Restore working directory os.chdir(pwd)
def mkpreproc_sess( sessid, outdir, fsdir, fsd="bold", perrun=True, persession=False, fwhm=5., update=True, force=False, sliceorder=None, surface="lhrh", mni3052mm=True, mni3051mm=False, nomc=False, nostc=False, nosmooth=False, nomask=False, noreg=False, noinorm=False, fsconfig=DEFAULT_FREESURFER_PATH, fslconfig=DEFAULT_FSL_PATH, verbose=0): """ Performs all the FreeSurfer fMRI preprocessing steps. Binding around the FreeSurfer's 'preproc-sess' command. Processing stages: * motion correction (MC) * slice-timing correction (STC) * smoothing * intensity normalization (INorm) * brain mask creation Note: MC and INorm require matlab but none of the other stages does. Parameters ---------- sessid: str (mandatory) the session id file or string. outdir: str (mandatory) the folder where the organized FreeSurfer data are. fsdir: str (mandatory) the FreeSurfer working home directory. fsd: str (optional, default 'bold') the folder name that contains the 'f.nii.gz' functional volume. perrun: bool (optional, default True) motion cor and reg to middle TP of each run. persession: bool (optional, default False) motion cor and reg to 1st TP of 1st run. fwhm: float (optional, default 5) FWHM : smoothing level (mm) update: bool (optional, default True) only run a stage if input is newer than output. force: bool (optional, default False) force reprocessing of all stages (turns off -update). sliceorder: list of int (optional, default None) turn on slice timing correction with the given slice order. surface: str (optional, default 'lhrh') 'self' or 'fsaverage' followed by hemi 'lh', 'rh' or 'lhrh'. mni3052mm: bool (optional, default True) sample raw data to mni305 at 2mm. mni3051mm: bool (optional, default False) sample raw data to mni305 at 1mm. nomc: bool (optional, default False) don't do motion correction. nostc: bool (optional, default False) don't do slice-timing correction. nosmooth: bool (optional, default False) don't do smoothing. nomask: bool (optional, default False) don't make brain mask. noreg: bool (optional, default False) don't do registration. noinorm: bool (optional, default False) don't do inorm. fsconfig: str (optional, default DEFAULT_FREESURFER_PATH) the FreeSurfer configuration batch. fslconfig: str (optional, default DEFAULT_FSL_PATH) the FSL configuration batch. verbose: int (optional, default 0) the verbosity level. Returns ------- lh_fsaverage: list of str left hemisphere of fsaverage. rh_fsaverage: list of str right hemisphere of fsaverage. sub_fsaverage: list of str volume of fsaverage (MNI305 space) - for subcortical analyses. bbr_sum: str a file containing functional-anatomical cross-modal registration QC score. The QC value will be between 0 and 1, with 0 being perfect and 1 being terrible. Generally, anything over 0.8 indicates that something is probably wrong. View registrations using the following command tkregister-sess -s sess02 -fsd bold -per-run. mc_plots: list of str a list of motion correction plot for each session. """ # Change current directory to deal with FreeeSurfer logs pwd = os.getcwd() os.chdir(outdir) # Check input parameters func_files = glob.glob(os.path.join(outdir, "*", fsd, "*", "f.nii.gz")) if verbose > 0: print("[info] Found {0} fMRI file(s) to be processed.".format( len(func_files))) if surface not in ("rh", "lh", "lhrh"): raise ValueError("Unknown '{0}' preproc-sess surface.".format(surface)) # Call FreeSurfer fMRI preproc cmd = ["preproc-sess", "-d", outdir, "-fsd", fsd, "-fwhm", str(fwhm), "-surface", "fsaverage", surface] if os.path.isfile(sessid): cmd += ["-sf", sessid] else: cmd += ["-s", sessid] if sliceorder is not None: cmd.extend(sliceorder) # -stc for value, name in ((perrun, "-per-run"), (persession, "-per-session"), (update, "-update"), (force, "-force"), (mni3052mm, "-mni305-2mm"), (mni3051mm, "-mni305-1mm"), (nomc, "-nomc"), (nostc, "-nostc"), (nosmooth, "-nosmooth"), (nomask, "-nomask"), (noreg, "-noreg"), (noinorm, "-noinorm")): if value: cmd.append(name) fsl_env = environment(fslconfig) wrap = FSWrapper(cmd, shfile=fsconfig, env=fsl_env, subjects_dir=fsdir) wrap() # QC # > Motion Correction plot: gives the vector motion at each time point for # each run. Note that it is always positive because this is a magnitude. # It is also 0 at the middle time point because the middle time point is # used as the reference. cmd = ["plot-twf-sess", "-d", outdir, "-fsd", fsd, "-mc"] if os.path.isfile(sessid): cmd += ["-sf", sessid] else: cmd += ["-s", sessid] wrap = FSWrapper(cmd, shfile=fsconfig, subjects_dir=fsdir) wrap() # > Functional-Anatomical Cross-modal Registration: a summary of # registration quality. The QC value will be between 0 and 1, with 0 being # perfect and 1 being terrible. Generally, anything over 0.8 indicates # that something is probably wrong. View registrations using the following # command tkregister-sess -s sess02 -fsd bold -per-run. cmd = ["tkregister-sess", "-d", outdir, "-fsd", fsd, "-per-run", "-bbr-sum"] if os.path.isfile(sessid): cmd += ["-sf", sessid] else: cmd += ["-s", sessid] wrap = FSWrapper(cmd, shfile=fsconfig, subjects_dir=fsdir) wrap() bbr_sum = os.path.join(outdir, "bbr_sum.txt") with open(bbr_sum, "wt") as open_file: open_file.write(wrap.stdout) # Restore working directory os.chdir(pwd) # Move FreeSurfer logs fs_log_dir = os.path.join(outdir, "log") log_dir = os.path.join(outdir, "logs") if not os.path.isdir(log_dir): os.mkdir(log_dir) if os.path.isdir(fs_log_dir): for basename in os.listdir(fs_log_dir): shutil.move(os.path.join(fs_log_dir, basename), os.path.join(log_dir, basename)) shutil.rmtree(fs_log_dir) # Outputs lh_fsaverage = sorted(glob.glob(os.path.join( outdir, "*", fsd, "*", "f*.fsaverage.lh.nii.gz"))) rh_fsaverage = sorted(glob.glob(os.path.join( outdir, "*", fsd, "*", "f*.fsaverage.rh.nii.gz"))) sub_fsaverage = sorted(glob.glob(os.path.join( outdir, "*", fsd, "*", "f*.mni305.*mm.nii.gz"))) mc_plots = glob.glob(os.path.join( outdir, "*", fsd, "*.mcdat.png")) return lh_fsaverage, rh_fsaverage, sub_fsaverage, bbr_sum, mc_plots
def mkmodel_sess( outdir, tr, contrasts_file, funcstem=None, fsd="bold", perrun=True, persession=False, fwhm=5., mni3052mm=True, mni3051mm=False, blocked_design=True, retinototy_design=False, abblocked_design=False, spmhrf=0, fslhrf=None, gammafit=None, ngammaderiv=None, fir=None, nconditions=None, refeventdur=None, polyfit=2, mcextreg=True, nuisreg=None, nskip=4, fsconfig=DEFAULT_FREESURFER_PATH, verbose=0): """ Configure First Level GLM Analysis for event-related and blocked design. Not yet implemented possibilities are abblocked and retinotopy design. Binding around the FreeSurfer's 'mkanalysis-sess ' and 'mkcontrast-sess' commands. Requires matlab or octave Parameters ---------- outdir: str (mandatory) the folder where the organized FreeSurfer data are. tr: float (mandatory) TR value in seconds. contrasts_file: str (mandatory) a JSON file that contains a list of contrasts with each contrast being a tuple of the form: ('name', 'stat', [condition list], [condition list ids], [weight list]). funcstem: str (optional, default None) override default basename, need to specify extension. The FWHM is no relevant when you have specified a funcstem fsd: str (optional, default 'bold') the folder name that contains the 'f.nii.gz' functional volume. perrun: bool (optional, default True) motion cor and reg to middle TP of each run. persession: bool (optional, default False) motion cor and reg to 1st TP of 1st run. fwhm: float (optional, default 5) FWHM : smoothing level (mm) mni3052mm: bool (optional, default True) sample raw data to mni305 at 2mm. mni3051mm: bool (optional, default False) sample raw data to mni305 at 1mm. spmhrf: int (optional, default 0) assume SPM HRF with nderiv derivatives. fslhrf: int (optional, default None) assume FSL HRF with nderiv derivatives gammafit: 2-uplet (optional, default None) assume IRF is a gamma function (gfDelta gfTau) -> (2.25 1.25). ngammaderiv: int (optional, default None) number of derivatives to gamma function. fir: 2-uplet (optional, default None) event prestimulus and total time window (sec) for FIR designs nconditions: int (optional, default None) number of conditions (excluding fixation). refeventdur: int (optional, default None) duration (sec) of reference event for scaling. polyfit: int (optional, default 2) fit trend with polynomial of order N: 0 mean offset, 1 temporal trend, 2 quadratic trend. mcextreg: bool (optional, default True) use motion parameters as external nuissance regressors. nuisreg: 2-uplet (optional, default None) external nuisance regressor file and number of regressors to include (extreg n). nskip: int (optional, default 4) skip the first N time points in each run fsconfig: str (optional, default DEFAULT_FREESURFER_PATH) the FreeSurfer configuration batch. verbose: int (optional, default 0) the verbosity level. Returns ------- analysis_names: list of str the configured model names. """ # Change current directory to deal with FreeeSurfer logs pwd = os.getcwd() os.chdir(outdir) # Check input parameters for path in [contrasts_file]: if not os.path.isfile(path): raise ValueError("'{0}' is not a valid file.".format(path)) if retinototy_design or abblocked_design: raise NotImplementedError("Selected design not yet supported.") # Load contrast description file with open(contrasts_file, "rt") as open_file: contrasts = json.load(open_file) # Call FreeSurfer fMRI firsl level GLM analysis: a different analysis is # needed for each space lh, rh, and mni305. analysis_names = [] for space in ["lh", "rh", "mni305"]: # Analysis Name - name used to reference this collection of # parameters. analysis_name = "odd.even.sm{0}.{1}".format(fwhm, space) analysis_names.append(analysis_name) if verbose > 0: print("[info] Performing analysis '{0}'...".format(analysis_name)) # Set preproc options cmd = ["mkanalysis-sess", "-fsd", fsd, "-paradigm", "odd.even.par", "-analysis", analysis_name, "-TR", str(tr), "-force"] if funcstem is None: cmd += ["-fwhm", str(fwhm)] else: cmd += ["-funcstem", funcstem] if space in ["lh", "rh"]: cmd += ["-surface", "fsaverage", space] else: cmd += ["-mni305"] for value, name in ((perrun, ["-per-run"]), (persession, ["-per-session"]), (mni3052mm, ["-mni305", "2"]), (mni3051mm, ["-mni305", "1"])): if value: cmd.extend(name) # Set design options cmd += ["-event-related"] for value, name in ((spmhrf, "-spmhrf"), (fslhrf, "-fslhrf"), (ngammaderiv, "-ngammaderiv"), (refeventdur, "-refeventdur"), (nconditions, "-nconditions")): if value is not None: cmd.extend([name, str(value)]) for value, name in ((gammafit, "-gammafit"), (fir, "-fir")): if value is not None: cmd.extend([name] + [str(e) for e in value]) # Set noise, drift, and temporal filtering options cmd += ["-polyfit", str(polyfit), "-nskip", str(nskip)] if mcextreg: cmd += ["-mcextreg"] if nuisreg is not None: cmd += ["-nuisreg", nuisreg[0], str(nuisreg[1])] # Create the analysis configuration file wrap = FSWrapper(cmd, shfile=fsconfig) wrap() # Create the contrasts for contrast_name, _, _, conditions, weights in contrasts: cmd = ["mkcontrast-sess", "-debug", "-analysis", analysis_name, "-contrast", contrast_name, "-ncond", str(nconditions), "-wcond"] wcond = numpy.zeros((nconditions,)) wcond[conditions] = weights cmd += [str(e) for e in wcond] wrap = FSWrapper(cmd, shfile=fsconfig, env=os.environ) wrap() # Restore working directory os.chdir(pwd) # Move FreeSurfer logs fs_log_dir = os.path.join(outdir, "log") log_dir = os.path.join(outdir, "logs") if not os.path.isdir(log_dir): os.mkdir(log_dir) if os.path.isdir(fs_log_dir): for basename in os.listdir(fs_log_dir): shutil.move(os.path.join(fs_log_dir, basename), os.path.join(log_dir, basename)) shutil.rmtree(fs_log_dir) return analysis_names
def trac_all(outdir, subjects_dir=None, temp_dir=None, fsconfig=DEFAULT_FREESURFER_PATH, fslconfig=DEFAULT_FSL_PATH): """ Anisotropy and diffusivity along the trajectory of a pathway. Parameters ---------- outdir: str Root directory where to create the subject's output directory. Created if not existing. subjects_dir: str, default None Path to the FreeSurfer subjects directory. Required if the environment variable $SUBJECTS_DIR is not set. temp_dir: str, default None Directory to use to store temporary files. By default OS tmp dir. fsconfig: str, default <pyfreesurfer.DEFAULT_FREESURFER_PATH> Path to the FreeSurfer configuration file. Returns ------- statdir: str The directory containing the FreeSurfer summary files. outlierfile: str A file that contains the subjects flagged as outliers. """ # FreeSurfer $SUBJECTS_DIR has to be passed or set as an env variable subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir) # Find all the subjects with a pathway stat file statdirs = glob.glob(os.path.join(subjects_dir, "*", "dpath")) subjects = set([path.split(os.sep)[-2] for path in statdirs]) subjects = " ".join(subjects) # Create directory for temporary files temp_dir = tempfile.mkdtemp(prefix="trac-all_", dir=temp_dir) # Create configuration file config_str = CONFIG_TEMPLATE.format(subjects_dir=subjects_dir, subjlist=subjects, dtroot=subjects_dir) path_config = os.path.join(temp_dir, "trac-all.dmrirc") with open(path_config, "w") as f: f.write(config_str) # Run Tracula preparation cmd_prep = ["trac-all", "-stat", "-c", path_config] FSWrapper(cmd_prep, shfile=fsconfig, subjects_dir=subjects_dir, add_fsl_env=True, fsl_sh=fslconfig)() # Move results to destination folder statdir = os.path.join(subjects_dir, "stats") shutil.move(statdir, outdir) # Clean tmp dir shutil.rmtree(temp_dir) # Detect outliers statdir = os.path.join(outdir, "stats") outlierfile = os.path.join(outdir, "outliers.json") logfiles = glob.glob(os.path.join(statdir, "*.log")) outliers = set() regex = r"^Found outlier path: .*" for path in logfiles: with open(path, "rt") as open_file: for match in re.findall(regex, open_file.read(), flags=re.MULTILINE): outliers.add(match.replace("Found outlier path: ", "")) with open(outlierfile, "wt") as open_file: json.dump(list(outliers), open_file, indent=4) return statdir, outlierfile
def mri_vol2surf(hemi, volume_file, out_texture_file, ico_order, dat_file, fsdir, sid, surface_name="white", fsconfig=DEFAULT_FREESURFER_PATH): """ Assigns values from a volume to each surface vertices. Wrapper around the FreeSurfer 'mri_vol2surf' command to create the described texture. Parameters ---------- hemi: str (mandatory) hemisphere ('lh' or 'rh'). volume_file: str (mandatory) input volume path. out_texture_file: str (mandatory) output texture file. ico_order: int (mandatory) icosahedron order in [0, 7] that will be used to generate the cortical surface texture at a specific tessalation (the corresponding cortical surface can be resampled using the 'pyfreesurfer.utils.surftools.resample_cortical_surface' function). dat_file: str (mandatory) structural to FreeSurfer space affine '.dat' transformation matrix file as computed by 'tkregister2'. fsdir: str (mandatory) FreeSurfer subjects directory 'SUBJECTS_DIR'. sid: str (mandatory) FreeSurfer subject identifier. surface_name: str (optional, default 'white') The surface we want to resample ('white' or 'pial'). fsconfig: str (optional) The FreeSurfer '.sh' config file. """ # Check input parameters for path in (volume_file, dat_file): if not os.path.isfile(path): raise ValueError("'{0}' is not a valid input file.".format(path)) for path in (fsdir, ): if not os.path.isdir(path): raise ValueError("'{0}' is not a valid directory.".format(path)) if hemi not in ["lh", "rh"]: raise ValueError("'{0}' is not a valid hemisphere value which must be " "in ['lh', 'rh']".format(hemi)) if surface_name not in ["white", "pial"]: raise ValueError("'{0}' is not a valid surface value which must be in " "['white', 'pial']".format(surface_name)) if ico_order < 0 or ico_order > 7: raise ValueError("'Ico order '{0}' is not in 0-7 " "range.".format(ico_order)) # Construct the FreeSurfer vol2surf command cmd = [ "mri_vol2surf", "--src", volume_file, "--out", out_texture_file, "--srcreg", dat_file, "--hemi", hemi, "--trgsubject", "ico", "--icoorder", "{0}".format(ico_order), "--surf", surface_name, "--sd", fsdir, "--srcsubject", sid, "--noreshape", "--out_type", "mgz" ] # Execute the FreeSurfer command recon = FSWrapper(cmd, shfile=fsconfig) recon()
def trac_all_longitudinal(outdir, subject_template_id, subject_timepoint_ids, dwis, bvalss, bvecss, bedpostx_dirs, subjects_dir=None, do_eddy=False, do_rotate_bvecs=True, do_bbregister=True, do_register_mni=True, temp_dir=None, fsconfig=DEFAULT_FREESURFER_PATH): """ Parameters ---------- outdir: str Root directory where to create the subject's output directories. Created if not existing. subject_template_id: str Identifier of the subject template. subject_timepoint_ids: str Identifiers of the subject for all the timepoints. dwis: list of str Paths to Nifti diffusion series. In the order corresponding to <subject_timepoint_ids>. bvalss: list of str Paths to b-values of diffusion series. In the order corresponding to <dwis>. bvecss: list of str Paths to diffusion-sensitized directions of diffusion series. In the order corresponding to <dwis>. bedpostx_dirs: list of str BedpostX output directories. In the order corresponding to <dwis>. subjects_dir: str, default None Path to the FreeSurfer longitudinal subjects directory. Required if the environment variable $SUBJECTS_DIR is not set. do_eddy: bool, default False Apply FSL eddy-current correction. do_rotate_bvecs: bool, default True Rotate bvecs to match eddy-current correction. do_bbregister: bool, default True Register diffusion to T1 using bbregister. do_register_mni: Register T1 to MNI. temp_dir: str, default None Set the root temporary directory. fsconfig: str, default <pyfreesurfer.DEFAULT_FREESURFER_PATH> Path to the FreeSurfer configuration file. Returns ------- subject_long_outdirs: list of str Path to longitudinal subject's output directories. """ # Check input arguments # Check that the user has passed non-empty lists of the same length list_args = [subject_timepoint_ids, dwis, bvalss, bvecss, bedpostx_dirs] are_all_lists = all(map(lambda x: isinstance(x, list), list_args)) all_same_size = len(set(map(len, list_args))) == 1 non_empty = len(subject_timepoint_ids) > 1 if not (are_all_lists & all_same_size & non_empty): raise ValueError("'subject_timepoint_ids', 'dwis', 'bvals', 'bvecs' " "and 'bedpostx_dirs' must be lists of IDs/paths.") # FreeSurfer $SUBJECTS_DIR has to be passed or set as an env variable subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir) # Check existence of input files/directories input_paths = dwis + bvalss + bvecss + bedpostx_dirs + [fsconfig] for path in input_paths: if not os.path.exists(path): raise ValueError("File or directory does not exist: %s" % path) # Create directory for temporary files temp_dir = tempfile.mkdtemp(prefix="trac-all_longitudinal_", dir=temp_dir) # The bvecs can be stored as a 3 x N or N x 3 matrix (N directions). # FreeSurfer requires the 2nd convention (one row per direction). # read_bvals_bvecs() always loads the bvecs file as a N x 3 numpy array, # save this numpy array in a temporary directory and use it as the # bvecs file to be sure to be in the right convention. bvecss_Nx3 = [] for tp_sid, bvals, bvecs in zip(subject_timepoint_ids, bvalss, bvecss): _, bvecs_array, _, _ = read_bvals_bvecs(bvals_path=bvals, bvecs_path=bvecs, min_bval=200.) bvecs_Nx3 = os.path.join(temp_dir, "bvecs_Nx3_%s" % tp_sid) numpy.savetxt(bvecs_Nx3, bvecs_array) bvecss_Nx3.append(bvecs_Nx3) # Create configuration file str_subjlist = " ".join(subject_timepoint_ids) str_baselist = " ".join([subject_template_id] * len(subject_timepoint_ids)) str_dwis = " ".join(dwis) str_bveclist = " ".join(bvecss_Nx3) str_bvallist = " ".join(bvalss) config_str = LONG_CONFIG_TEMPLATE.format(subjects_dir=subjects_dir, subjlist=str_subjlist, baselist=str_baselist, dtroot=outdir, dcmlist=str_dwis, bveclist=str_bveclist, bvallist=str_bvallist, doeddy=do_eddy, dorotbvecs=do_rotate_bvecs, doregbbr=do_bbregister, doregmni=do_register_mni) path_config = os.path.join(temp_dir, "trac-all.long.dmrirc") with open(path_config, "w") as f: f.write(config_str) # For each timepoint of subject: subject_long_outdirs = [] for tp_sid, bedpostx_dir in zip(subject_timepoint_ids, bedpostx_dirs): # Create <outdir>/<tp sid>.long.<template id> if not existing long_sid = "%s.long.%s" % (tp_sid, subject_template_id) long_outdir = os.path.join(outdir, long_sid) if not os.path.isdir(long_outdir): os.makedirs(long_outdir) subject_long_outdirs.append(long_outdir) # Tracula requires the bedpostX files to be stored in # <outdir>/<tp sid>.long.<template id>/dmri.bedpostX create_bedpostx_organization(subject_id=long_sid, subjects_dir=outdir, bedpostx_dir=bedpostx_dir) # Run Tracula preparation cmd_prep = ["trac-all", "-prep", "-c", path_config] FSWrapper(cmd_prep, shfile=fsconfig, subjects_dir=subjects_dir, add_fsl_env=True)() # Tracula pathways tractography cmd_path = ["trac-all", "-path", "-c", path_config] FSWrapper(cmd_path, shfile=fsconfig, subjects_dir=subjects_dir, add_fsl_env=True)() # Clean tmp dir shutil.rmtree(temp_dir) return subject_long_outdirs
def recon_all_custom_wm_mask(subject_id, wm_mask, keep_orig=True, subjects_dir=None, temp_dir=None, fsconfig=DEFAULT_FREESURFER_PATH): """ Assuming you have run recon-all (at least upto wm.mgz creation), this function allows to rerun recon-all using a custom white matter mask. The mask has to be in the subject's FreeSurfer space (1mm iso + aligned with brain.mgz) with values in [0; 1] (i.e. probability of being white matter). Parameters ---------- subject_id: str Identifier of subject. wm_mask: str Path to the custom white matter mask. It has to be in the subject's FreeSurfer space (1mm iso + aligned with brain.mgz) with values in [0; 1] (i.e. probability of being white matter). For example, tt can be the 'brain_pve_2.nii.gz" white matter probability map created by FSL Fast. keep_orig: bool, default True Save original 'wm.seg.mgz' as 'wm.seg.orig.mgz' instead of overwriting it. subjects_dir: str, default None Path to the FreeSurfer subjects directory. Required if the environment variable $SUBJECTS_DIR is not set. temp_dir: str, default None Directory to use to store temporary files. By default OS tmp dir. fsconfig: str, default <pyfreesurfer.DEFAULT_FREESURFER_PATH> The FreeSurfer configuration batch. """ # FreeSurfer $SUBJECTS_DIR has to be passed or set as an env variable subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir) # Check existence of the subject's directory subject_dir = os.path.join(subjects_dir, subject_id) if not os.path.isdir(subject_dir): ValueError("Directory does not exist: %s" % subject_dir) # Create temporary directory to store intermediate files temp_dir = tempfile.mkdtemp(prefix="recon_all_custom_wm_mask_", dir=temp_dir) # Change input mask range of values: [0-1] to [0-110] wm_mask_0_110 = os.path.join(temp_dir, "wm_mask_0_110.nii.gz") cmd_1 = ["mris_calc", "-o", wm_mask_0_110, wm_mask, "mul", "110"] FSWrapper(cmd_1, shfile=fsconfig)() # If requested save original wm.seg.mgz as wm.seg.orig.mgz wm_seg_mgz = os.path.join(subject_dir, "mri", "wm.seg.mgz") if keep_orig: save_as = os.path.join(subject_dir, "mri", "wm.seg.orig.mgz") shutil.move(wm_seg_mgz, save_as) # Write the new wm.seg.mgz, FreeSurfer requires MRI_UCHAR type cmd_2 = ["mri_convert", wm_mask_0_110, wm_seg_mgz, "-odt", "uchar"] FSWrapper(cmd_2, shfile=fsconfig)() # Clean tmp dir shutil.rmtree(temp_dir) # Rerun recon-all cmd_3 = ["recon-all", "-autorecon2-wm", "-autorecon3", "-s", subject_id] FSWrapper(cmd_3, shfile=fsconfig, subjects_dir=subjects_dir)() return subject_dir
def mri_convert(fsdir, regex, outdir, destdirname="convert", reslice=True, interpolation="interpolate", fsconfig=DEFAULT_FREESURFER_PATH): """ Export FreeSurfer '.mgz' image in Nifti format. Convert in native space: the destination image is resliced like the 'rawavg.mgz' file if the reslice option is set. The converted file will then have a '.native' suffix. Binding over the FreeSurfer's 'mri_convert' command. Parameters ---------- fsdir: str (mandatory) The FreeSurfer home directory with all the subjects. regex: str (mandatory) A regular expression used to locate the files to be converted from the 'fsdir' directory. outdir: str (mandatory) The conversion destination folder. destdirname: str (optional, default 'convert') The name of the folder where each subject converted volumes will be saved. If None, don't create a sub folder. reslice: bool (optional default False) If True reslice the input images like the raw image. interpolation: str (optional default interpolate) The interpolation method: interpolate|weighted|nearest|cubic. fsconfig str (optional) The FreeSurfer configuration batch. Returns ------- niftifiles: list of str The converted nifti files. """ # Check the input parameters for path in (fsdir, outdir): if not os.path.isdir(path): raise ValueError("'{0}' is not a valid directory.".format(path)) # Check the interpolation method if interpolation not in ["interpolate", "weighted", "nearest", "cubic"]: raise ValueError( "'{0}' is not a valid interpolation method.".format(interpolation)) # Get the images to convert from the regex inputs = glob.glob(os.path.join(fsdir, regex)) # Convert each input file niftifiles = [] for input_file in inputs: # Create the output directory subject = input_file.replace(fsdir, "") subject = subject.lstrip(os.sep).split(os.sep)[0] if destdirname is not None: subjoutdir = os.path.join(outdir, subject, destdirname) else: subjoutdir = outdir if not os.path.isdir(subjoutdir): os.makedirs(subjoutdir) # Create the FreeSurfer command basename = os.path.basename(input_file).replace(".mgz", "") cmd = ["mri_convert", "--resample_type", interpolation] # "--out_orientation", "RAS"] if reslice: reference_file = os.path.join(fsdir, subject, "mri", "rawavg.mgz") if not os.path.isfile(reference_file): raise ValueError("'{0}' does not exists, can't reslice image " "'{1}'.".format(reference_file, input_file)) cmd += ["--reslice_like", reference_file] basename = basename + ".native" converted_file = os.path.join(subjoutdir, basename + ".nii.gz") niftifiles.append(converted_file) cmd += [input_file, converted_file] # Execute the FreeSurfer command recon = FSWrapper(cmd, shfile=fsconfig) recon() return niftifiles
def freesurfer_bbregister_t1todif(outdir, subject_id, nodif_brain, subjects_dir=None, fs_sh=DEFAULT_FREESURFER_PATH, fsl_sh=DEFAULT_FSL_PATH): """ Compute DWI to T1 transformation and project the T1 to the diffusion space without resampling. Parameters ---------- outdir: str Directory where to output. subject_id: str Subject id used with FreeSurfer 'recon-all' command. nodif_brain: str Path to the preprocessed brain-only DWI volume. subjects_dir: str or None, default None Path to the FreeSurfer subjects directory. Required if the FreeSurfer environment variable (i.e. $SUBJECTS_DIR) is not set. fs_sh: str, default NeuroSpin path Path to the Bash script setting the FreeSurfer environment fsl_sh: str, default NeuroSpin path Path to the Bash script setting the FSL environment. Returns ------- t1_brain_to_dif: str The anatomical image in the diffusion space (without resampling). dif2anat_dat, dif2anat_mat: str The DWI to T1 transformation in FreeSurfer or FSL space respectivelly. """ # ------------------------------------------------------------------------- # STEP 0 - Check arguments # FreeSurfer subjects_dir subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir) # Check input paths paths_to_check = [nodif_brain, fs_sh, fsl_sh] for p in paths_to_check: if not os.path.exists(p): raise ValueError("File or directory does not exist: %s" % p) # ------------------------------------------------------------------------- # STEP 1 - Compute T1 <-> DWI rigid transformation # Register diffusion to T1 dif2anat_dat = os.path.join(outdir, "dif2anat.dat") dif2anat_mat = os.path.join(outdir, "dif2anat.mat") cmd_1a = [ "bbregister", "--s", subject_id, "--mov", nodif_brain, "--reg", dif2anat_dat, "--fslmat", dif2anat_mat, "--dti", "--init-fsl" ] FSWrapper(cmd_1a, subjects_dir=subjects_dir, shfile=fs_sh, add_fsl_env=True, fsl_sh=fsl_sh)() # Align FreeSurfer T1 brain to diffusion without downsampling fs_t1_brain = os.path.join(subjects_dir, subject_id, "mri", "brain.mgz") t1_brain_to_dif = os.path.join(outdir, "fs_t1_brain_to_dif.nii.gz") cmd_1b = [ "mri_vol2vol", "--mov", nodif_brain, "--targ", fs_t1_brain, "--inv", "--no-resample", "--o", t1_brain_to_dif, "--reg", dif2anat_dat, "--no-save-reg" ] FSWrapper(cmd_1b, shfile=fs_sh)() return t1_brain_to_dif, dif2anat_dat, dif2anat_mat
def aparcstats2table(fsdir, outdir, fsconfig=DEFAULT_FREESURFER_PATH): """ Generate text/ascii tables of freesurfer parcellation stats data '?h.aparc.stats'. This can then be easily imported into a spreadsheet and/or stats program. Binding over the FreeSurfer's 'aparcstats2table' command. Parameters ---------- fsdir: (mandatory) The freesurfer working directory with all the subjects. outdir: str (mandatory) The statistical destination folder. fsconfig: str (optional) The freesurfer configuration batch. Return ------ statfiles: list of str The freesurfer summary stats. """ # Check input parameters for path in (fsdir, outdir): if not os.path.isdir(path): raise ValueError("'{0}' is not a valid directory.".format(path)) # Parameter that will contain the output stats statfiles = [] # Fist find all the subjects with a stat dir statdirs = glob.glob(os.path.join(fsdir, "*", "stats")) subjects = [item.lstrip(os.sep).split("/")[-2] for item in statdirs] # Save the FreeSurfer current working directory and set the new one fscwd = None if "SUBJECTS_DIR" in os.environ: fscwd = os.environ["SUBJECTS_DIR"] os.environ["SUBJECTS_DIR"] = fsdir # Create the output stat directory fsoutdir = os.path.join(outdir, "stats") if not os.path.isdir(fsoutdir): os.mkdir(fsoutdir) # Call freesurfer for hemi in ["lh", "rh"]: for meas in [ "area", "volume", "thickness", "thicknessstd", "meancurv", "gauscurv", "foldind", "curvind" ]: statfile = os.path.join( fsoutdir, "aparc_stats_{0}_{1}.csv".format(hemi, meas)) statfiles.append(statfile) cmd = ["aparcstats2table", "--subjects"] + subjects + [ "--hemi", hemi, "--meas", meas, "--tablefile", statfile, "--delimiter", "comma", "--parcid-only" ] recon = FSWrapper(cmd, shfile=fsconfig) recon() if recon.exitcode != 0: raise FreeSurferRuntimeError(recon.cmd[0], " ".join(recon.cmd[1:]), recon.stderr + recon.stdout) # Restore the FreeSurfer working directory if fscwd is not None: os.environ["SUBJECTS_DIR"] = fscwd return statfiles
def resample_cortical_surface(fsdir, regex, outdir, destdirname="convert", orders=[4, 5, 6, 7], surface_name="white", fsconfig=DEFAULT_FREESURFER_PATH): """ Resamples one cortical surface onto an icosahedron. Resample the white or pial FreeSurfer cotical surface using the 'mri_surf2surf' command. Map also the associated annotation file. Can resample at different icosahedron order which specifies the size of the icosahedron according to the following table: Order Number of Vertices 0 12 1 42 2 162 3 642 4 2562 5 10242 6 40962 7 163842 Binding over the FreeSurfer's 'mri_surf2surf' command. Parameters ---------- fsdir: str (mandatory) The freesurfer home directory with all the subjects. regex: str (mandatory) A regular expression used to locate the surface files to be converted from the 'fsdir' directory. outdir: str (optional, default None) The destination folder. destdirname: str (optional, default 'convert') The name of the folder where each subject resample cortical surface will be saved. orders: list of int The icosahedron orders. surface_name: str (optional, default 'white') The surface we want to resample ('white' or 'pial'). fsconfig: str (optional) The freesurfer configuration batch. Returns ------- resamplefiles: list of str The resample surfaces. annotfiles: list of str The resample annotations. """ # Check input parameters for path in (fsdir, outdir): if not os.path.isdir(path): raise ValueError("'{0}' is not a valid directory.".format(path)) if surface_name not in ["white", "pial", "graymid"]: raise ValueError("'{0}' is not a valid surface value which must be in " "['white', 'pial']".format(surface_name)) norders = numpy.asarray(orders) if norders.min() < 0 or norders.max() > 7: raise ValueError("'At least one value in {0} is not in 0-7 " "range.".format(orders)) # Get all the subjects with the specified surface surfaces = glob.glob(os.path.join(fsdir, regex)) # Go through all the subjects with the desired surface resamplefiles = [] annotfiles = [] for surf in surfaces: # Get some information based on the surface path subject_id = surf.split("/")[-3] hemi = os.path.basename(surf).split(".")[0] convertdir = os.path.join(outdir, subject_id, destdirname) if not os.path.isdir(convertdir): os.makedirs(convertdir) # Go through all specified orders for level in orders: # Construct the FS surface map command convertfile = os.path.join( convertdir, "{0}.{1}.{2}".format(hemi, surface_name, level)) resamplefiles.append(convertfile) recon = FSWrapper([], shfile=fsconfig) if version.parse(recon.version or "") >= version.parse("6.0.0"): origfile = os.path.join(fsdir, subject_id, "mri", "orig.mgz") cmd = [ "mri_surf2surf", "--sval-xyz", surface_name, "--srcsubject", subject_id, "--trgsubject", "ico", "--trgicoorder", str(level), "--tval", convertfile, "--tval-xyz", origfile, "--hemi", hemi, "--sd", fsdir ] else: cmd = [ "mri_surf2surf", "--sval-xyz", surface_name, "--srcsubject", subject_id, "--trgsubject", "ico", "--trgicoorder", str(level), "--tval", convertfile, "--tval-xyz", "--hemi", hemi, "--sd", fsdir ] # Execute the FS command recon.cmd = cmd recon() # Construct the FS label map command annotfile = os.path.join(convertdir, "{0}.aparc.annot.{1}".format(hemi, level)) annotfiles.append(annotfile) if not os.path.isfile(annotfile): svalannot = os.path.join(fsdir, subject_id, "label", "{0}.aparc.annot".format(hemi)) cmd = [ "mri_surf2surf", "--srcsubject", subject_id, "--trgsubject", "ico", "--trgicoorder", str(level), "--hemi", hemi, "--sval-annot", svalannot, "--tval", annotfile, "--sd", fsdir ] # Execute the FS command recon = FSWrapper(cmd, shfile=fsconfig) recon() # Remove duplicate annotation files annotfiles = list(set(annotfiles)) return sorted(resamplefiles), sorted(annotfiles)
def probtrackx2_connectome_complete(outdir, subject_id, lh_surf, rh_surf, nodif_brain, nodif_brain_mask, bedpostx_dir, nsamples, nsteps, steplength, subjects_dir=None, loopcheck=True, cthr=0.2, fibthresh=0.01, distthresh=0.0, sampvox=0.0, fs_sh=DEFAULT_FREESURFER_PATH, fsl_sh=DEFAULT_FSL_PATH): """ Compute the connectome of a given tesellation, like the FreeSurfer, using ProbTrackx2. Requirements: - brain masks for the preprocessed DWI: nodif_brain and nodif_brain_mask. - FreeSurfer: result of recon-all on the T1. - FSL Bedpostx: computed for the preprocessed DWI. Connectome construction strategy: - Pathways are constructed from 'constitutive points' and not from endpoints. A pathway is the result of 2 samples propagating in opposite directions from a seed point. It is done using the --omatrix3 option of Probtrackx2. - The seed mask is the mask of WM voxels that are neighbors (12-connexity) of nodes. - The stop mask is the inverse of white matter, i.e. a sample stops propagating as soon as it leaves the white matter. Note: --randfib refers to initialization of streamlines only (i.e. the very first step) and only affects voxels with more than one fiber reconstructed: randfib==0, only sample from the strongest fiber randfib==1, randomly sample from all fibers regardless of strength that are above --fibthresh randfib==2, sample fibers stronger than --fibthresh in proportion to their strength (in my opinion, this is the best choice) randfib==3, sample all fibers randomly regardless of whether or not they are above --fibthresh. Parameters ---------- outdir: str Directory where to output. subject_id: str Subject id used with FreeSurfer 'recon-all' command. lh_surf: str The left hemisphere surface. rh_surf: str The left hemisphere surface. nodif_brain: str Path to the preprocessed brain-only DWI volume. nodif_brain_mask: str Path to the brain binary mask. bedpostx_dir: str Bedpostx output directory. nsamples: int Number of samples per voxel to initiate in the seed mask. nsteps: int Maximum number of steps for a given sample. steplength: int Step size in mm. subjects_dir: str or None, default None Path to the FreeSurfer subjects directory. Required if the FreeSurfer environment variable (i.e. $SUBJECTS_DIR) is not set. cthr: float, optional Probtrackx2 option. fibthresh, distthresh, sampvox: float, optional Probtrackx2 options. loopcheck: bool, optional Probtrackx2 option. fs_sh: str, default NeuroSpin path Path to the Bash script setting the FreeSurfer environment fsl_sh: str, default NeuroSpin path Path to the Bash script setting the FSL environment. Returns ------ coords: str The connectome coordinates. weights: str The connectome weights. """ # ------------------------------------------------------------------------- # STEP 0 - Check arguments # FreeSurfer subjects_dir subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir) # Check input paths paths_to_check = [ nodif_brain, nodif_brain_mask, bedpostx_dir, fs_sh, fsl_sh ] for p in paths_to_check: if not os.path.exists(p): raise ValueError("File or directory does not exist: %s" % p) # Create <outdir> if not existing if not os.path.isdir(outdir): os.makedirs(outdir) # ------------------------------------------------------------------------- # STEP 1 - Compute T1 <-> DWI rigid transformation # FreeSurfer T1 to Nifti fs_t1_brain = os.path.join(subjects_dir, subject_id, "mri", "brain.mgz") t1_brain = os.path.join(outdir, "t1_brain.nii.gz") cmd_1a = ["mri_convert", fs_t1_brain, t1_brain] FSWrapper(cmd_1a, shfile=fs_sh)() # Register diffusion to T1 dif2anat_dat = os.path.join(outdir, "dif2anat.dat") dif2anat_mat = os.path.join(outdir, "dif2anat.mat") nodif_brain_reg = os.path.join(outdir, "nodif_brain_to_t1.nii.gz") cmd_1b = [ "bbregister", "--s", subject_id, "--mov", nodif_brain, "--reg", dif2anat_dat, "--fslmat", dif2anat_mat, "--dti", "--init-fsl", "--o", nodif_brain_reg ] FSWrapper(cmd_1b, subjects_dir=subjects_dir, shfile=fs_sh, add_fsl_env=True, fsl_sh=fsl_sh)() # Invert dif2anat transform m = numpy.loadtxt(dif2anat_mat) m_inv = numpy.linalg.inv(m) anat2dif_mat = os.path.join(outdir, "anat2dif.mat") numpy.savetxt(anat2dif_mat, m_inv) # ------------------------------------------------------------------------- # STEP 2 - Create the masks for Probtrackx2 # White matter mask aparc_aseg = os.path.join(subjects_dir, subject_id, "mri", "aparc+aseg.mgz") wm_mask = os.path.join(outdir, "wm_mask.nii.gz") mri_binarize(inputfile=aparc_aseg, outputfile=wm_mask, match=None, wm=True, inv=False, fsconfig=fs_sh) # Stop mask is inverse of white matter mask stop_mask = os.path.join(outdir, "inv_wm_mask.nii.gz") mri_binarize(inputfile=aparc_aseg, outputfile=stop_mask, match=None, wm=True, inv=True, fsconfig=fs_sh) # Create seed mask seed_mask = wm_mask # Create target masks: the white surface white_surf = os.path.join(outdir, "white.asc") cmd_2a = ["mris_convert", "--combinesurfs", lh_surf, rh_surf, white_surf] FSWrapper(cmd_2a, subjects_dir=subjects_dir, shfile=fs_sh)() # ------------------------------------------------------------------------- # STEP 7 - Run Probtrackx2 probtrackx2(dir=outdir, forcedir=True, seedref=t1_brain, xfm=anat2dif_mat, invxfm=dif2anat_mat, samples=os.path.join(bedpostx_dir, "merged"), mask=nodif_brain_mask, seed=seed_mask, omatrix3=True, target3=white_surf, stop=stop_mask, nsamples=nsamples, nsteps=nsteps, steplength=steplength, loopcheck=loopcheck, cthr=cthr, fibthresh=fibthresh, distthresh=distthresh, sampvox=sampvox, pd=True, randfib=2, shfile=fsl_sh) coords = os.path.join(outdir, "coords_for_fdt_matrix3") weights = os.path.join(outdir, "fdt_matrix3.dot") return coords, weights
def interhemi_surfreg(hemi, outdir, fsdir, sid, template_file=DEFAULT_TEMPLATE_SYM_PATH, destname="surfreg", fsconfig=DEFAULT_FREESURFER_PATH): """ Surface-based interhemispheric registration by aplying an existing atlas, the 'fsaverage_sym'. Reference Greve, Douglas N., Lise Van der Haegen, Qing Cai, Steven Stufflebeam, Mert R. Sabuncu, Bruce Fischl, and Marc Bysbaert. "A surface-based analysis of language lateralization and cortical asymmetry." (2013). Journal of Cognitive Neuroscience 25.9: 1477-1492. Parameters ---------- hemi: str (mandatory) hemisphere ('lh' or 'rh'). outdir: str (mandatory) the destination folder. fsdir: str (mandatory) FreeSurfer subjects directory 'SUBJECTS_DIR'. sid: str (mandatory) FreeSurfer subject identifier. template_file: str (optional, default DEFAULT_TEMPLATE_SYM_PATH) path to the 'fsaverage_sym' template. destname: str (optional, default 'destname') the name of the folder where the results will be stored. fsconfig: str (optional, default DEFAULT_FREESURFER_PATH) The FreeSurfer '.sh' config file. Returns ------- xhemidir: str the symetrized hemispheres. spherefile: str the registration file to the template. """ # Check input parameters if hemi not in ["lh", "rh"]: raise ValueError("'{0}' is not a valid hemisphere value which must be " "in ['lh', 'rh']".format(hemi)) subjfsdir = os.path.join(fsdir, sid) for path in (subjfsdir, outdir): if not os.path.isdir(path): raise ValueError("'{0}' is not a valid directory.".format(path)) # Symlink input data in destination foler wdir = os.path.join(outdir, destname) symlinks = [] dest_template_file = os.path.join(outdir, "fsaverage_sym") if not os.path.islink(dest_template_file): os.symlink(template_file, dest_template_file) symlinks.append(dest_template_file) if os.path.isdir(wdir): shutil.rmtree(wdir) os.mkdir(wdir) for basename in os.listdir(subjfsdir): if basename == "scripts": continue path = os.path.join(subjfsdir, basename) destpath = os.path.join(wdir, basename) symlinks.append(destpath) os.symlink(path, destpath) # Create the commands os.environ["SUBJECTS_DIR"] = outdir cmds = [] sym_template_file = os.path.join( wdir, "surf", "{0}.fsaverage_sym.sphere.reg".format(hemi)) if os.path.isfile(sym_template_file): os.remove(sym_template_file) cmds += [[ "surfreg", "--s", destname, "--t", "fsaverage_sym", "--{0}".format(hemi) ], ["xhemireg", "--s", destname], [ "surfreg", "--s", destname, "--t", "fsaverage_sym", "--{0}".format(hemi), "--xhemi" ]] # Execute the FS commands for cmd in cmds: recon = FSWrapper(cmd, shfile=fsconfig) recon() # Remove symliks for path in symlinks: os.unlink(path) # Get outputs xhemidir = os.path.join(wdir, "xhemi") spherefile = os.path.join(subjfsdir, "surf", "{0}.fsaverage_sym.sphere.reg".format(hemi)) return xhemidir, spherefile
def mitk_gibbs_tractogram(outdir, subject_id, dwi, bvals, bvecs, nodif_brain=None, nodif_brain_mask=None, subjects_dir=None, sh_order=4, reg_factor=0.006, nb_iterations=5e8, particle_length=0, particle_width=0, particle_weight=0, start_temperature=0.1, end_temperature=0.001, inex_energy_balance=0, min_fiber_length=20, curvature_threshold=45, tempdir=None, fs_sh=DEFAULT_FREESURFER_PATH, fsl_sh=DEFAULT_FSL_PATH): """ Wrapper to the MITK global tractography tool (Gibbs Tracking). Parameters ---------- outdir: str Directory where to output. subject_id: str Subject id used with FreeSurfer 'recon-all' command. dwi: str Path to the diffusion-weighted images (Nifti required). bvals: str Path to the bvalue list. bvecs: str Path to the list of diffusion-sensitized directions. nodif_brain: str, default None Diffusion brain-only Nifti volume with bvalue ~ 0. If not passed, it is generated automatically by averaging all the b0 volumes of the DWI. nodif_brain_mask: str, default None Path to the Nifti brain binary mask in diffusion. If not passed, it is created with MRtrix 'dwi2mask'. subjects_dir: str or None, default None Path to the FreeSurfer subjects directory. Required if the FreeSurfer environment variable (i.e. $SUBJECTS_DIR) is not set. sh_order: int, default 4 Qball reconstruction spherical harmonics order. reg_factor: float, default Qball reconstruction regularization factor.. nb_iterations: int, default 5E8 Gibbs tracking number of iterations. particle_length: float, default 0 Gibbs tracking particle length, selected automatically if 0. particle_width: float, default 0 Gibbs tracking particle width, selected automatically if 0. particle_weight: float, default 0 Gibbs tracking particle weight, selected automatically if 0. start_temperature: float, default 0.1 Gibbs tracking start temperature. end_temperature: float, default 0.001 Gibbs tracking end temperature. inex_energy_balance: float, default 0 Gibbs tracking weighting between in/ext energies. min_fiber_length: int, default 20 Minimum fiber length in mm. Fibers that are shorter are discarded. curvature_threshold: int, default 45 Maximum fiber curvature in degrees. tempdir: str Path to the directory where temporary directories should be written. It should be a partition with 5+ GB available. fs_sh: str, default NeuroSpin path Path to the Bash script setting the FreeSurfer environment fsl_sh: str, default NeuroSpin path Path to the Bash script setting the FSL environment. Returns ------- mitk_tractogram: str The computed global tractogram in VTK format. """ # ------------------------------------------------------------------------- # STEP 0 - Check arguments # FreeSurfer subjects_dir subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir) # Check input paths paths_to_check = [dwi, bvals, bvecs, nodif_brain_mask, fs_sh, fsl_sh] for p in [nodif_brain, nodif_brain_mask]: if p is not None: paths_to_check.append(p) for p in paths_to_check: if not os.path.exists(p): raise ValueError("File or directory does not exist: %s" % p) # Create <outdir> and/or <tempdir> if not existing for directory in [outdir, tempdir]: if not os.path.isdir(directory): os.makedirs(directory) # ------------------------------------------------------------------------- # STEP 1 - Compute DWI to T1 transformation and project the T1 # to the diffusion space without resampling. # If user has not provided a 'nodif_brain_mask', compute one with # MRtrix 'dwi2mask' if nodif_brain_mask is None: nodif_brain_mask = os.path.join(outdir, "nodif_brain_mask.nii.gz") cmd_1a = ["dwi2mask", dwi, nodif_brain_mask, "-fslgrad", bvecs, bvals] subprocess.check_call(cmd_1a) # If user has not provided a 'nodif_brain', apply 'nodif_brain_mask' to # mean b=0 volume if nodif_brain is None: # Extract b=0 volumes and compute mean b=0 volume b0s = os.path.join(outdir, "b0s.nii.gz") mean_b0 = os.path.join(outdir, "mean_b0.nii.gz") mrtrix_extract_b0s_and_mean_b0(dwi=dwi, b0s=b0s, mean_b0=mean_b0, bvals=bvals, bvecs=bvecs, nb_threads=1) # Apply nodif_brain_mask to dwi nodif_brain = os.path.join(outdir, "nodif_brain.nii.gz") cmd_1b = ["mri_mask", mean_b0, nodif_brain_mask, nodif_brain] FSWrapper(cmd_1b, shfile=fs_sh)() # Register nodif_brain to FreeSurfer T1 t1_brain_to_dif, dif2anat_dat, _ = freesurfer_bbregister_t1todif( outdir=outdir, subject_id=subject_id, nodif_brain=nodif_brain, subjects_dir=subjects_dir, fs_sh=fs_sh, fsl_sh=fsl_sh) # ------------------------------------------------------------------------- # STEP 2 - Apply brain mask to DWI before Qball reconstruction dwi_brain = os.path.join(outdir, "dwi_brain.nii.gz") cmd_4 = ["fslmaths", dwi, "-mas", nodif_brain_mask, dwi_brain] FSLWrapper(cmd_4, shfile=fsl_sh)() # MITK requires the Nifti to have an .fslgz extension and the bvals/bvecs # to have the same name with .bvals/.bvecs extension dwi_brain_fslgz = os.path.join(outdir, "dwi_brain.fslgz") shutil.copyfile(dwi_brain, dwi_brain_fslgz) shutil.copyfile(bvals, "%s.bvals" % dwi_brain_fslgz) shutil.copyfile(bvecs, "%s.bvecs" % dwi_brain_fslgz) # ------------------------------------------------------------------------- # STEP 3 - Qball reconstruction qball_coefs = os.path.join(outdir, "sphericalHarmonics_CSA_Qball.qbi") cmd_5 = [ "MitkQballReconstruction.sh", "-i", dwi_brain_fslgz, "-o", qball_coefs, "-sh", "%i" % sh_order, "-r", "%f" % reg_factor, "-csa", "--mrtrix" ] # TODO: create MITK wrapper with LD_LIBRARY_PATH and QT_PLUGIN_PATH subprocess.check_call(cmd_5) # ------------------------------------------------------------------------- # STEP 4 - Create white matter probability map with FSL Fast # Create directory for temporary files fast_tempdir = tempfile.mkdtemp(prefix="FSL_fast_", dir=tempdir) base_outpath = os.path.join(fast_tempdir, "brain") cmd_6 = ["fast", "-o", base_outpath, t1_brain_to_dif] FSLWrapper(cmd_6, shfile=fsl_sh)() # Save the white matter probability map wm_prob_map = os.path.join(outdir, "wm_prob_map.nii.gz") shutil.copyfile(base_outpath + "_pve_2.nii.gz", wm_prob_map) # Clean temporary directory shutil.rmtree(fast_tempdir) # ------------------------------------------------------------------------- # STEP 5 - Gibbs tracking (global tractography) # Create XML parameter file root = ElementTree.Element("global_tracking_parameter_file") root.set("version", "1.0") attributes = { "iterations": "%i" % nb_iterations, "particle_length": "%f" % particle_length, "particle_width": "%f" % particle_width, "particle_weight": "%f" % particle_weight, "temp_start": "%f" % start_temperature, "temp_end": "%f" % end_temperature, "inexbalance": "%f" % inex_energy_balance, "fiber_length": "%i" % min_fiber_length, "curvature_threshold": "%i" % curvature_threshold } ElementTree.SubElement(root, "parameter_set", attrib=attributes) tree = ElementTree.ElementTree(element=root) path_xml = os.path.join(outdir, "parameters.gtp") tree.write(path_xml) # Run tractography mitk_tractogram = os.path.join(outdir, "fibers.fib") cmd_7 = [ "MitkGibbsTracking.sh", "-i", qball_coefs, "-p", path_xml, "-m", wm_prob_map, "-o", mitk_tractogram, "-s", "MRtrix" ] subprocess.check_call(cmd_7) return mitk_tractogram
def mrtrix_tractogram( outdir, tempdir, subject_id, dwi, bvals, bvecs, nb_threads, global_tractography=False, mtracks=None, maxlength=None, cutoff=None, seed_gmwmi=False, sift_mtracks=None, sift2=False, nodif_brain=None, nodif_brain_mask=None, fast_t1_brain=None, subjects_dir=None, mif_gz=True, delete_raw_tracks=False, delete_dwi_mif=True, fs_sh=DEFAULT_FREESURFER_PATH, fsl_sh=DEFAULT_FSL_PATH): """ Compute the connectome using MRtrix. Requirements: - FreeSurfer: result of recon-all on the T1. - a T1 parcellation that defines the nodes of the connectome, it has to be in the FreeSurfer space (i.e. aligned with <subjects dir>/<subject>/mri/brain.mgz), e.g. aparc+aseg from FreeSurfer. Parameters ---------- outdir: str Path to directory where to output. tempdir: str Path to the directory where temporary directories should be written. It should be a partition with 5+ GB available. subject_id: str Subject identifier. dwi: str Path to the diffusion-weighted images (Nifti required). bvals: str Path to the bvalue list. bvecs: str Path to the list of diffusion-sensitized directions. nb_threads: int Number of threads. global_tractography: bool, default False If True run global tractography (tckglobal) instead of local (tckgen). mtracks: int, default None For non-global tractography only. Number of millions of tracks of the raw tractogram. maxlength: int, default None For non-global tractography only. Max fiber length in mm. cutoff: float, default None For non-global tractography only. FOD amplitude cutoff, stopping criteria. seed_gmwmi: bool, default False For non-global tractography only. Set this option if you want to activate the '-seed_gmwmi' option of MRtrix 'tckgen', to seed from the GM/WM interface. Otherwise, and by default, the seeding is done in white matter ('-seed_dynamic' option). sift_mtracks: int, default None For non-global tractography only. Number of millions of tracks to keep with SIFT. If not set, SIFT is not applied. sift2: bool, default False For non-global tractography only. To activate SIFT2. nodif_brain: str, default None Diffusion brain-only Nifti volume with bvalue ~ 0. If not passed, it is generated automatically by averaging all the b0 volumes of the DWI. nodif_brain_mask: str, default None Path to the Nifti brain binary mask in diffusion. If not passed, it is created with MRtrix 'dwi2mask'. fast_t1_brain: str, default None By default FSL FAST is run on the FreeSurfer 'brain.mgz'. If you want the WM probability map to be computed from another T1, pass the T1 brain-only volume. Note that it has to be aligned with diffusion. This argument is useful for HCP, where some FreeSurfer 'brain.mgz' cannot be processed by FSL FAST. subjects_dir: str, default None Path to the FreeSurfer subjects directory. Required if the environment variable $SUBJECTS_DIR is not set. mif_gz: bool, default True Use compressed MIF files (.mif.gz) instead of .mif to save space. delete_raw_tracks: bool, default False Delete the raw tracks (<outdir>/<mtracks>M.tck) at the end of processing, to save space. delete_dwi_mif: bool, default True Delete <outdir>/DWI.mif(.gz) at the end of processing, which is a copy of the input <dwi> in the MIF format, to save space. fs_sh: str, default NeuroSpin path Path to the Bash script setting the FreeSurfer environment fsl_sh: str, default NeuroSpin path Path to the Bash script setting the FSL environment. Returns ------- tracks: str The generated tractogram. sift_tracks: str The SIFT filtered tractogram. sift2_weights: str The SIFT2 tractogram associated weights. """ # ------------------------------------------------------------------------- # STEP 0 - Check arguments # FreeSurfer $SUBJECTS_DIR has to be passed or set as an env variable subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir) # Use compressed MIF files or not MIF_EXT = ".mif.gz" if mif_gz else ".mif" # Is SIFT to be applied sift = (sift_mtracks is not None) # Check input and optional paths paths_to_check = [dwi, bvals, bvecs, fsl_sh] for p in [nodif_brain, nodif_brain_mask, fast_t1_brain]: if p is not None: paths_to_check.append(p) for p in paths_to_check: if not os.path.exists(p): raise ValueError("File or directory does not exist: %s" % p) # Identify whether the DWI acquisition is single or multi-shell _, _, nb_shells, _ = read_bvals_bvecs(bvals, bvecs, min_bval=200.) is_multi_shell = nb_shells > 1 # Check compatibility of arguments if global_tractography: if not is_multi_shell: raise ValueError("MRtrix global tractography is only applicable " "to multi shell data.") if seed_gmwmi: raise ValueError("'seed_gmwmi' cannot be applied when requesting " "global tractography.") if sift or sift2: raise ValueError("SIFT or SIFT2 are not meant to be used with " "global tractography.") else: value_of_required_arg = dict(mtracks=mtracks, maxlength=maxlength, cutoff=cutoff) for required_arg, value in value_of_required_arg.items(): if value is None: raise ValueError("When 'global_tractography' is set to False " "%s is required." % required_arg) # Create <outdir> and/or <tempdir> if not existing for directory in [outdir, tempdir]: if not os.path.isdir(directory): os.makedirs(directory) # ------------------------------------------------------------------------- # STEP 1 - Format DWI and compute nodif brain and nodif brain mask if # not provided # Convert DWI to MRtrix format dwi_mif = os.path.join(outdir, "DWI.mif") cmd_1a = ["mrconvert", dwi, dwi_mif, "-fslgrad", bvecs, bvals, "-datatype", "float32", "-stride", "0,0,0,1", "-nthreads", "%i" % nb_threads, "-failonwarn"] subprocess.check_call(cmd_1a) # If user has not provided a 'nodif_brain_mask', compute one with # MRtrix 'dwi2mask' if nodif_brain_mask is None: nodif_brain_mask = os.path.join(outdir, "nodif_brain_mask.nii.gz") cmd_1b = ["dwi2mask", dwi_mif, nodif_brain_mask] subprocess.check_call(cmd_1b) # If user has not provided a 'nodif_brain', apply 'nodif_brain_mask' to # mean b=0 volume if nodif_brain is None: # Extract b=0 volumes and compute mean b=0 volume b0s = os.path.join(outdir, "b0s.nii.gz") mean_b0 = os.path.join(outdir, "mean_b0.nii.gz") mrtrix_extract_b0s_and_mean_b0(dwi=dwi_mif, b0s=b0s, mean_b0=mean_b0, nb_threads=nb_threads) # Apply nodif_brain_mask to dwi nodif_brain = os.path.join(outdir, "nodif_brain.nii.gz") cmd_1b = ["mri_mask", mean_b0, nodif_brain_mask, nodif_brain] FSWrapper(cmd_1b, shfile=fs_sh)() # ------------------------------------------------------------------------- # STEP 2 - Register DWI to T1 using FreeSurfer bbregister # - compute the rigid transformation # - apply transformation to align T1 without downsampling t1_brain_to_dif, dif2anat_dat, _ = freesurfer_bbregister_t1todif( outdir=outdir, subject_id=subject_id, nodif_brain=nodif_brain, subjects_dir=subjects_dir, fs_sh=fs_sh, fsl_sh=fsl_sh) # ------------------------------------------------------------------------- # STEP 3 - "5 tissue types" segmentation # Generate the 5TT image based on a FSL FAST five_tissues = os.path.join(outdir, "5TT%s" % MIF_EXT) fast_t1_brain = t1_brain_to_dif if fast_t1_brain is None else fast_t1_brain cmd_3 = ["5ttgen", "fsl", fast_t1_brain, five_tissues, "-premasked", "-tempdir", tempdir, "-nthreads", "%i" % nb_threads] FSLWrapper(cmd_3, env=os.environ, shfile=fsl_sh)() # ------------------------------------------------------------------------- # STEP 4 - Estimation of the response function of fibers in each voxel if is_multi_shell: rf_wm = os.path.join(outdir, "RF_WM.txt") rf_gm = os.path.join(outdir, "RF_GM.txt") rf_csf = os.path.join(outdir, "RF_CSF.txt") cmd_4 = ["dwi2response", "msmt_5tt", dwi_mif, five_tissues, rf_wm, rf_gm, rf_csf] else: rf = os.path.join(outdir, "RF.txt") cmd_4 = ["dwi2response", "tournier", dwi_mif, rf] rf_voxels = os.path.join(outdir, "RF_voxels%s" % MIF_EXT) cmd_4 += ["-voxels", rf_voxels, "-tempdir", tempdir, "-nthreads", "%i" % nb_threads] subprocess.check_call(cmd_4) # ------------------------------------------------------------------------- # STEP 5 - Compute FODs wm_fods = os.path.join(outdir, "WM_FODs%s" % MIF_EXT) if is_multi_shell: gm_mif = os.path.join(outdir, "GM%s" % MIF_EXT) csf_mif = os.path.join(outdir, "CSF%s" % MIF_EXT) cmd_5 = ["dwi2fod", "msmt_csd", dwi_mif, rf_wm, wm_fods, rf_gm, gm_mif, rf_csf, csf_mif] else: cmd_5 = ["dwi2fod", "csd", dwi_mif, rf, wm_fods] cmd_5 += ["-mask", nodif_brain_mask, "-nthreads", "%i" % nb_threads, "-failonwarn"] subprocess.check_call(cmd_5) # ------------------------------------------------------------------------- # STEP 6 - Image to visualize for multi-shell if is_multi_shell: wm_fods_vol0 = os.path.join(outdir, "WM_FODs_vol0%s" % MIF_EXT) cmd_6a = ["mrconvert", wm_fods, wm_fods_vol0, "-coord", "3", "0", "-nthreads", "%i" % nb_threads] subprocess.check_call(cmd_6a) tissueRGB_mif = os.path.join(outdir, "tissueRGB%s" % MIF_EXT) cmd_6b = ["mrcat", csf_mif, gm_mif, wm_fods_vol0, tissueRGB_mif, "-axis", "3", "-nthreads", "%i" % nb_threads, "-failonwarn"] subprocess.check_call(cmd_6b) # ------------------------------------------------------------------------- # STEP 7 - Tractography: tckglobal or tckgen if global_tractography: tracks = os.path.join(outdir, "global.tck") global_fod = os.path.join(outdir, "fod%s" % MIF_EXT) fiso_mif = os.path.join(outdir, "fiso%s" % MIF_EXT) cmd_7 = ["tckglobal", dwi_mif, rf_wm, "-riso", rf_csf, "-riso", rf_gm, "-mask", nodif_brain_mask, "-niter", "1e8", "-fod", global_fod, "-fiso", fiso_mif, tracks] else: # Anatomically Constrained Tractography: # iFOD2 algorithm with backtracking and crop fibers at GM/WM interface tracks = os.path.join(outdir, "%iM.tck" % mtracks) cmd_7 = ["tckgen", wm_fods, tracks, "-act", five_tissues, "-backtrack", "-crop_at_gmwmi", "-maxlength", "%i" % maxlength, "-number", "%dM" % mtracks, "-cutoff", "%f" % cutoff] # Requested seeding strategy: -seed_gmwmi or -seed_dynamic if seed_gmwmi: gmwmi_mask = os.path.join(outdir, "gmwmi_mask%s" % MIF_EXT) cmd_7b = ["5tt2gmwmi", five_tissues, gmwmi_mask] subprocess.check_call(cmd_7b) cmd_7 += ["-seed_gmwmi", gmwmi_mask] else: cmd_7 += ["-seed_dynamic", wm_fods] cmd_7 += ["-nthreads", "%i" % nb_threads, "-failonwarn"] subprocess.check_call(cmd_7) # ------------------------------------------------------------------------- # STEP 8 - Filter tracts with SIFT if requested sift_tracks = None if sift: sift_tracks = os.path.join(outdir, "%iM_SIFT.tck" % sift_mtracks) cmd_8 = ["tcksift", tracks, wm_fods, sift_tracks, "-act", five_tissues, "-term_number", "%iM" % sift_mtracks, "-nthreads", "%i" % nb_threads, "-failonwarn"] subprocess.check_call(cmd_8) # ------------------------------------------------------------------------- # STEP 9 - run SIFT2 if requested (compute weights of fibers) sift2_weights = None if sift2: sift2_weights = os.path.join(outdir, "sift2_weights.txt") cmd_9 = ["tcksift2", tracks, wm_fods, sift2_weights, "-act", five_tissues, "-nthreads", "%i" % nb_threads, "-failonwarn"] subprocess.check_call(cmd_9) # ------------------------------------------------------------------------- # STEP 10 - clean if requested if delete_raw_tracks: os.remove(tracks) tracks = None if delete_dwi_mif: os.remove(dwi_mif) dwi_mif = None else: if mif_gz: subprocess.check_call(["gzip", dwi_mif]) dwi_mif += ".gz" return tracks, sift_tracks, sift2_weights
def tractstats2table(fsdir, outdir, fsconfig=DEFAULT_FREESURFER_PATH): """ Generate text/ascii tables of FreeSurfer tracula pathways anisotropy and diffusivity summary from 'dpath/<hemi>.<path>/pathstats.overall.txt' and 'dpath/<hemi>.<path>/pathstats.byvoxel.txt'. This can then be easily imported into a spreadsheet and/or stats program. Binding over the FreeSurfer's 'tractstats2table' command. Parameters ---------- fsdir: (mandatory) The freesurfer working directory with all the subjects. outdir: str (mandatory) The destination folder. fsconfig: str (optional) The FreeSurfer configuration batch. Return ------ statfiles: list of str The FreeSurfer summary files. """ # Check input parameters for path in (fsdir, outdir): if not os.path.isdir(path): raise ValueError("'{0}' is not a valid directory.".format(path)) # Parameter that will contain the output group statistics statfiles = [] # Fist find all the subjects with a pathway stat file pathstatfiles = glob.glob( os.path.join(fsdir, "*", "dpath", "*", "pathstats.overall.txt")) # Split these files by pathway names pathwayfiles = {} for path in pathstatfiles: pathwayname = path.split(os.sep)[-2] if pathwayname not in pathwayfiles: pathwayfiles[pathwayname] = [] pathwayfiles[pathwayname].append(path) # Save the FreeSurfer current working directory and set the new one fscwd = None if "SUBJECTS_DIR" in os.environ: fscwd = os.environ["SUBJECTS_DIR"] os.environ["SUBJECTS_DIR"] = fsdir # Create the output stat directory fsoutdir = os.path.join(outdir, "overall_stats") if not os.path.isdir(fsoutdir): os.mkdir(fsoutdir) # Call freesurfer for name, files in pathwayfiles.items(): statfile = os.path.join(fsoutdir, "{0}.csv".format(name)) statfiles.append(statfile) cmd = ["tractstats2table", "--inputs" ] + files + ["--overall", "--tablefile", statfile] recon = FSWrapper(cmd, shfile=fsconfig) recon() # Restore the FreeSurfer working directory if fscwd is not None: os.environ["SUBJECTS_DIR"] = fscwd return statfiles
def conformed_to_native_space(fsdir, regex, outdir, fsconfig=DEFAULT_FREESURFER_PATH): """ Return the conformed to native transformation files. Create a registration matrix between the conformed space (orig.mgz) and the native anatomical (rawavg.mgz). Binding over the FreeSurfer's 'tkregister2' command. Parameters ---------- fsdir: str (mandatory) The FreeSurfer working directory with all the subjects. regex: str A regular expression used to locate the mri files to be converted from the 'fsdir' directory. outdir: str The destination folder. fsconfig: str (optional) The FreeSurfer configuration batch. Returns ------- trffiles: list of str The conformed to native transformation files. """ # Check the input parameters for path in (fsdir, outdir): if not os.path.isdir(path): raise ValueError("'{0}' is not a valid directory.".format(path)) # Get all the subjects with a 'mri' directory mridirs = glob.glob(os.path.join(fsdir, regex)) # Go through all the subjects with the desired folder trffiles = [] for path_mri in mridirs: # Get some information based on the folder path subject_id = path_mri.rstrip(os.path.sep).split(os.path.sep)[-2] convertdir = os.path.join(outdir, subject_id, "convert") if not os.path.isdir(convertdir): os.makedirs(convertdir) # Check that the two images of interest are present rawfile = os.path.join(path_mri, "rawavg.mgz") origfile = os.path.join(path_mri, "orig.mgz") for path in (rawfile, origfile): if not os.path.isfile(path): raise ValueError("In folder '{0}' can't find file " "'{1}'.".format(path_mri, path)) # Construct the FreeSurfer command trffile = os.path.join(convertdir, "register.native.dat") trffiles.append(trffile) cmd = [ "tkregister2", "--mov", rawfile, "--targ", origfile, "--reg", trffile, "--noedit", "--regheader" ] # Execute the FreeSurfer command recon = FSWrapper(cmd, shfile=fsconfig) recon() return trffiles
def recon_all(fsdir, anatfile, sid, reconstruction_stage="all", resume=False, t2file=None, flairfile=None, fsconfig=DEFAULT_FREESURFER_PATH): """ Performs all the FreeSurfer cortical reconstruction steps. Binding around the FreeSurfer's 'recon-all' command. Processing stages: * Motion Correction and Conform * NU (Non-Uniform intensity normalization) * Talairach transform computation * Intensity Normalization 1 * Skull Strip * EM Register (linear volumetric registration) * CA Intensity Normalization * CA Non-linear Volumetric Registration * Remove Neck * LTA with Skull * CA Label (Volumetric Labeling, ie Aseg) and Statistics * Intensity Normalization 2 (start here for control points) * White matter segmentation * Edit WM With ASeg * Fill (start here for wm edits) * Tessellation (begins per-hemisphere operations) * Smooth1 * Inflate1 * QSphere * Automatic Topology Fixer * Final Surfs (start here for brain edits for pial surf) * Smooth2 * Inflate2 * Spherical Mapping * Spherical Registration * Spherical Registration, Contralateral hemisphere * Map average curvature to subject * Cortical Parcellation - Desikan_Killiany and Christophe (Labeling) * Cortical Parcellation Statistics * Cortical Ribbon Mask * Cortical Parcellation mapping to Aseg Parameters ---------- fsdir: str (mandatory) The FreeSurfer working directory with all the subjects. anatfile: str (mandatory) The input anatomical image to be segmented with FreeSurfer. sid: str (mandatory) The current subject identifier. reconstruction_stage: str (optional, default 'all') The FreeSurfer reconstruction stage that will be launched. resume: bool (optional, default False) If true, try to resume the recon-all. This option is also usefull if custom segmentation is used in recon-all. t2file: str (optional, default None) Specify the path to a T2 image that will be used to improve the pial surfaces. flairfile: str (optional, default None) Specify the path to a FLAIR image that will be used to improve the pial surfaces. fsconfig: str (optional) The FreeSurfer configuration batch. Returns ------- subjfsdir: str Path to the resulting FreeSurfer segmentation. """ # Check input parameters if not os.path.isdir(fsdir): raise ValueError("'{0}' FreeSurfer home directory does not " "exists.".format(fsdir)) if reconstruction_stage not in ("all", "autorecon1", "autorecon2", "autorecon2-cp", "autorecon2-wm", "autorecon2-pial", "autorecon3"): raise ValueError("Unsupported '{0}' recon-all reconstruction " "stage.".format(reconstruction_stage)) # Call FreeSurfer segmentation cmd = [ "recon-all", "-{0}".format(reconstruction_stage), "-subjid", sid, "-i", anatfile, "-sd", fsdir ] if t2file is not None: cmd.extend(["-T2", t2file, "-T2pial"]) if flairfile is not None: cmd.extend(["-FLAIR", t2file, "-FLAIRpial"]) if resume: cmd[1] = "-make all" recon = FSWrapper(cmd, shfile=fsconfig) recon() subjfsdir = os.path.join(fsdir, sid) return subjfsdir
def resample_cortical_surface( fsdir, regex, outdir, destdirname="convert", orders=[4, 5, 6, 7], surface_name="white", fsconfig=DEFAULT_FREESURFER_PATH): """ Resamples one cortical surface onto an icosahedron. Resample the white or pial FreeSurfer cotical surface using the 'mri_surf2surf' command. Map also the associated annotation file. Can resample at different icosahedron order which specifies the size of the icosahedron according to the following table: Order Number of Vertices 0 12 1 42 2 162 3 642 4 2562 5 10242 6 40962 7 163842 Binding over the FreeSurfer's 'mri_surf2surf' command. Parameters ---------- fsdir: str (mandatory) The freesurfer home directory with all the subjects. regex: str (mandatory) A regular expression used to locate the surface files to be converted from the 'fsdir' directory. outdir: str (optional, default None) The destination folder. destdirname: str (optional, default 'convert') The name of the folder where each subject resample cortical surface will be saved. orders: list of int The icosahedron orders. surface_name: str (optional, default 'white') The surface we want to resample ('white' or 'pial'). fsconfig: str (optional) The freesurfer configuration batch. Returns ------- resamplefiles: list of str The resample surfaces. annotfiles: list of str The resample annotations. """ # Check input parameters for path in (fsdir, outdir): if not os.path.isdir(path): raise ValueError("'{0}' is not a valid directory.".format(path)) if surface_name not in ["white", "pial", "graymid"]: raise ValueError("'{0}' is not a valid surface value which must be in " "['white', 'pial']".format(surface_name)) norders = numpy.asarray(orders) if norders.min() < 0 or norders.max() > 7: raise ValueError("'At least one value in {0} is not in 0-7 " "range.".format(orders)) # Get all the subjects with the specified surface surfaces = glob.glob(os.path.join(fsdir, regex)) # Go through all the subjects with the desired surface resamplefiles = [] annotfiles = [] for surf in surfaces: # Get some information based on the surface path subject_id = surf.split("/")[-3] hemi = os.path.basename(surf).split(".")[0] convertdir = os.path.join(outdir, subject_id, destdirname) if not os.path.isdir(convertdir): os.makedirs(convertdir) # Go through all specified orders for level in orders: # Construct the FS surface map command convertfile = os.path.join(convertdir, "{0}.{1}.{2}".format( hemi, surface_name, level)) resamplefiles.append(convertfile) recon = FSWrapper([], shfile=fsconfig) if version.parse(recon.version or "") >= version.parse("6.0.0"): origfile = os.path.join(fsdir, subject_id, "mri", "orig.mgz") cmd = ["mri_surf2surf", "--sval-xyz", surface_name, "--srcsubject", subject_id, "--trgsubject", "ico", "--trgicoorder", str(level), "--tval", convertfile, "--tval-xyz", origfile, "--hemi", hemi, "--sd", fsdir] else: cmd = ["mri_surf2surf", "--sval-xyz", surface_name, "--srcsubject", subject_id, "--trgsubject", "ico", "--trgicoorder", str(level), "--tval", convertfile, "--tval-xyz", "--hemi", hemi, "--sd", fsdir] # Execute the FS command recon.cmd = cmd recon() # Construct the FS label map command annotfile = os.path.join(convertdir, "{0}.aparc.annot.{1}".format( hemi, level)) annotfiles.append(annotfile) if not os.path.isfile(annotfile): svalannot = os.path.join(fsdir, subject_id, "label", "{0}.aparc.annot".format(hemi)) cmd = ["mri_surf2surf", "--srcsubject", subject_id, "--trgsubject", "ico", "--trgicoorder", str(level), "--hemi", hemi, "--sval-annot", svalannot, "--tval", annotfile, "--sd", fsdir] # Execute the FS command recon = FSWrapper(cmd, shfile=fsconfig) recon() # Remove duplicate annotation files annotfiles = list(set(annotfiles)) return sorted(resamplefiles), sorted(annotfiles)
def trac_all(outdir, subject_id, dwi, bvals, bvecs, bedpostx_dir, subjects_dir=None, do_eddy=False, do_rotate_bvecs=True, do_bbregister=True, do_register_mni=True, temp_dir=None, fsconfig=DEFAULT_FREESURFER_PATH): """ Parameters ---------- outdir: str Root directory where to create the subject's output directory. Created if not existing. subject_id: str Identifier of subject. dwi: str Path to input Nifti diffusion-weighted volumes. bvals: str Path to b-values of diffusion-weighted volumes. bvecs: str Path to diffusion-sensitized directions. bedpostx_dir: str BedpostX output directory. subjects_dir: str, default None Path to the FreeSurfer subjects directory. Required if the environment variable $SUBJECTS_DIR is not set. do_eddy: bool, default False Apply FSL eddy-current correction. do_rotate_bvecs: bool, default True Rotate bvecs to match eddy-current correction. do_bbregister: bool, default True Register diffusion to T1 using bbregister. do_register_mni: Register T1 to MNI. temp_dir: str, default None Directory to use to store temporary files. By default OS tmp dir. fsconfig: str, default <pyfreesurfer.DEFAULT_FREESURFER_PATH> Path to the FreeSurfer configuration file. Returns ------- subject_outdir: str Path to subject's output directory. """ # FreeSurfer $SUBJECTS_DIR has to be passed or set as an env variable subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir) # Check existence of input files/directories for path in [dwi, bvals, bvecs, bedpostx_dir, fsconfig]: if not os.path.exists(path): raise ValueError("File or directory does not exist: %s" % path) # Load bvecs and number of b0 volumes _, bvecs_array, _, nb_b0s = read_bvals_bvecs(bvals_path=bvals, bvecs_path=bvecs, min_bval=200.) # Create directory <outdir>/<subject_id> subject_outdir = os.path.join(outdir, subject_id) if not os.path.isdir(subject_outdir): os.makedirs(subject_outdir) # Create directory for temporary files temp_dir = tempfile.mkdtemp(prefix="trac-all_", dir=temp_dir) # The bvecs can be stored as a 3 x N or N x 3 matrix (N directions). # FreeSurfer requires the 2nd convention (one row per direction). # read_bvals_bvecs() always loads the bvecs file as a N x 3 numpy array, # save this numpy array in a temporary directory and use it as the # bvecs file to be sure to be in the right convention. bvecs_Nx3 = os.path.join(temp_dir, "bvecs_Nx3") numpy.savetxt(bvecs_Nx3, bvecs_array) # Create configuration file config_str = CONFIG_TEMPLATE.format(subjects_dir=subjects_dir, subjlist=subject_id, dtroot=outdir, dcmlist=dwi, bveclist=bvecs_Nx3, bvallist=bvals, doeddy=do_eddy, dorotbvecs=do_rotate_bvecs, doregbbr=do_bbregister, doregmni=do_register_mni) path_config = os.path.join(temp_dir, "trac-all.dmrirc") with open(path_config, "w") as f: f.write(config_str) # Run Tracula preparation cmd_prep = ["trac-all", "-prep", "-c", path_config] FSWrapper(cmd_prep, shfile=fsconfig, subjects_dir=subjects_dir, add_fsl_env=True)() # Tracula requires the BedpostX files to be stored in # <outdir>/<subject_id>/dmri.bedpostX create_bedpostx_organization(subject_id=subject_id, subjects_dir=outdir, bedpostx_dir=bedpostx_dir) # Tracula pathways tractography cmd_path = ["trac-all", "-path", "-c", path_config] FSWrapper(cmd_path, shfile=fsconfig, subjects_dir=subjects_dir, add_fsl_env=True)() # Clean tmp dir shutil.rmtree(temp_dir) return subject_outdir