def test_normal_execution(self, mock_isdir): """ Test the normal behaviour of the function. """ # Set the mocked functions returned values mock_isdir.side_effect = [True, False] # Test execution subjects_dir = get_or_check_freesurfer_subjects_dir( subjects_dir="/my/path/mock_sdir")
def create_bedpostx_organization(subject_id, bedpostx_dir, subjects_dir=None): """ Tracula requires the BedpostX output files to be stored in the FreeSurfer folder of the subject: <subjects_dir>/<subject_id>/dmri.bedpostX Since we run Bedpostx outside of FreeSurfer/Tracula, this function creates the needed file organization using symbolic links. Parameters ---------- subject_id: str Identifier of subject. 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. Returns ------- fs_bedpostx_dir: str Path to the FreeSurfer BedpostX 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 that subject's dir exists 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) # Create the FreeSurfer BedpostX directory if not existing fs_bedpostx_dir = os.path.join(subject_dir, "dmri.bedpostX") if not os.path.isdir(fs_bedpostx_dir): os.mkdir(fs_bedpostx_dir) # Create symbolic links to all BedpostX files for fn in os.listdir(bedpostx_dir): source = os.path.join(bedpostx_dir, fn) target = os.path.join(fs_bedpostx_dir, fn) os.symlink(source, target) return fs_bedpostx_dir
def create_bedpostx_organization(subject_id, bedpostx_dir, subjects_dir=None): """ Tracula requires the BedpostX output files to be stored in the FreeSurfer folder of the subject: <subjects_dir>/<subject_id>/dmri.bedpostX Since we run Bedpostx outside of FreeSurfer/Tracula, this function creates the needed file organization using symbolic links. Parameters ---------- subject_id: str Identifier of subject. 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. Returns ------- fs_bedpostx_dir: str Path to the FreeSurfer BedpostX 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 that subject's dir exists 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) # Create the FreeSurfer BedpostX directory if not existing fs_bedpostx_dir = os.path.join(subject_dir, "dmri.bedpostX") if not os.path.isdir(fs_bedpostx_dir): os.mkdir(fs_bedpostx_dir) # Create symbolic links to all BedpostX files for fn in os.listdir(bedpostx_dir): source = os.path.join(bedpostx_dir, fn) target = os.path.join(fs_bedpostx_dir, fn) os.symlink(source, target) return fs_bedpostx_dir
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 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 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 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 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 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 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 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 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
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 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): """ Pathway reconstruction. 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
def probtrackx2_connectome( outdir, tempdir, subject_id, t1_parc, t1_parc_lut, connectome_lut, nodif_brain, nodif_brain_mask, bedpostx_dir, nsamples, nsteps, steplength, fix_freesurfer_subcortical=False, subjects_dir=None, loopcheck=True, cthr=0.2, fibthresh=0.01, distthresh=0.0, sampvox=0.0, snapshots=True, fs_sh=DEFAULT_FREESURFER_PATH, fsl_sh=DEFAULT_FSL_PATH): """ Compute the connectome of a given parcellation, like the FreeSurfer aparc+aseg segmentation, 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. - 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. 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. Parameters ---------- outdir: str 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 id used with FreeSurfer 'recon-all' command. t1_parc: str Path to the parcellation that defines the nodes of the connectome, e.g. aparc+aseg.mgz from FreeSurfer. t1_parc_lut: str Path to the Look Up Table for the passed parcellation in the FreeSurfer LUT format. If you T1 parcellation is from FreeSurfer, this will most likely be <$FREESURFER_HOME>/FreeSurferColorLUT.txt. connectome_lut: str Path to a Look Up Table in the FreeSurfer LUT format, listing the regions from the parcellation to use as nodes in the connectome. The region names should match the ones used in the <t1_parc_lut> LUT and the integer labels should be the row/col positions in the connectome. Alternatively it can be set to 'Lausanne2008' to use the predefined LUT for the Lausanne 2008 atlas, which is based on the FreeSurfer aparc+aseg parcellation. 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. fix_freesurfer_subcortical: bool, default False If the <t1_parc> is aparc+aseg or aparc.a2009s+aseg from FreeSurfer, set this option to True, to recompute the subcortical segmentations of the 5 structures that are uncorrectly segmented by FreeSurfer, using FSL FIRST. 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. snapshots: bool, default True If True, create PNG snapshots for QC. 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 ------- connectome_file: str The generated connectome. labels_file: str The coonectome associated labels. connectome_snap_file: str A grphical representation of the connectome. """ # ------------------------------------------------------------------------- # STEP 0 - Check arguments # FreeSurfer subjects_dir subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir) if connectome_lut.lower() == "lausanne2008": module_dir = os.path.dirname(os.path.abspath(__file__)) connectome_lut = os.path.join(module_dir, "Lausanne2008LUT.txt") # Check input paths paths_to_check = [t1_parc, t1_parc_lut, connectome_lut, 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> and/or <tempdir> if not existing for directory in [outdir, tempdir]: if not os.path.isdir(directory): os.makedirs(directory) # ------------------------------------------------------------------------- # 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, dif2anat_mat = 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) # 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 - Convert LUT # Change integer labels in the LUT so that the each label corresponds # to the row/col position in the connectome nodes = os.path.join(outdir, "nodes.nii.gz") cmd_2 = ["labelconvert", t1_parc, t1_parc_lut, connectome_lut, nodes, "-nthreads", "0", "-failonwarn"] subprocess.check_call(cmd_2) # ------------------------------------------------------------------------- # STEP 3 - If the T1 parcellation is aparc+aseg or aparc.a2009s+aseg # from FreeSurfer, this option allows the recompute the subcortical # segmentations of 5 structures that are uncorrectly segmented by # FreeSurfer, using FSL FIRST if fix_freesurfer_subcortical: fixed_nodes = os.path.join(outdir, "nodes_fixSGM.nii.gz") nodes = fix_freesurfer_subcortical_parcellation(parc=nodes, t1_brain=t1_brain, lut=connectome_lut, output=fixed_nodes, tempdir=tempdir, nb_threads=0, fsl_sh=fsl_sh) # ------------------------------------------------------------------------- # STEP 4 - 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") cmd_4a = ["mri_binarize", "--i", aparc_aseg, "--o", wm_mask, "--wm"] FSWrapper(cmd_4a, shfile=fs_sh)() # Stop mask is inverse of white matter mask stop_mask = os.path.join(outdir, "inv_wm_mask.nii.gz") cmd_4b = ["mri_binarize", "--i", aparc_aseg, "--o", stop_mask, "--wm", "--inv"] FSWrapper(cmd_4b, shfile=fs_sh)() # Create target mask: a mask of all nodes target_mask = os.path.join(outdir, "target_mask.nii.gz") cmd_4c = ["mri_binarize", "--i", nodes, "--o", target_mask, "--min", "1"] FSWrapper(cmd_4c, shfile=fs_sh)() # Dilate target mask by one voxel (12-connexity) target_mask_dil = os.path.join(outdir, "target_mask_dilated.nii.gz") cmd_4d = ["mri_morphology", target_mask, "dilate", "1", target_mask_dil] FSWrapper(cmd_4d, shfile=fs_sh)() # Create seed mask: white matter voxels near nodes (target regions) # Intersect dilated target mask and white matter mask # -> white matter voxels neighbor (12-connectivity) to node voxels seed_mask = os.path.join(outdir, "wm_nodes_interface_mask.nii.gz") cmd_4e = ["mri_and", wm_mask, target_mask_dil, seed_mask] FSWrapper(cmd_4e, shfile=fs_sh)() # ------------------------------------------------------------------------- # STEP 7 - Run Probtrackx2 probtrackx2_dir = os.path.join(outdir, "probtrackx2") probtrackx2(dir=probtrackx2_dir, 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=nodes, stop=stop_mask, nsamples=nsamples, nsteps=nsteps, steplength=steplength, loopcheck=loopcheck, cthr=cthr, fibthresh=fibthresh, distthresh=distthresh, sampvox=sampvox, shfile=fsl_sh) # ------------------------------------------------------------------------ # STEP 8 - Create NODExNODE connectivity matrix for nodes from <t1_parc> connectome_file, labels_file = voxel_to_node_connectivity( probtrackx2_dir=probtrackx2_dir, nodes=nodes, connectome_lut=connectome_lut, outdir=outdir) # ------------------------------------------------------------------------ # STEP 9 - Create a connectome snapshot if requested if snapshots: connectome_snap_file = os.path.join(outdir, "connectome.png") connectome_snapshot(connectome_file, connectome_snap_file, labels=labels_file, transform=numpy.log1p, dpi=300, labels_size=4, colorbar_title="log(# of tracks)") return connectome_file, labels_file, connectome_snap_file