def test_normal_execution(self, mock_loadtxt): """ Test the normal behaviour of the function. """ # Set the mocked functions returned values bvecs = numpy.array([[0, 0, 0], [0, 0, 0], [1, 0, 0], [0, 1, 0]]).T bvals = numpy.array([0, 0, 1500, 150]) mock_loadtxt.side_effect = lambda x: {"dwi.bval": bvals, "dwi.bvec": bvecs}[x] # Test execution output_files = read_bvals_bvecs("dwi.bval", "dwi.bvec", min_bval=100.) self.assertTrue(numpy.allclose(output_files[0], bvals)) self.assertTrue(numpy.allclose(output_files[1], bvecs.T)) self.assertTrue(output_files[2] == 2) self.assertTrue(output_files[3] == 2)
def test_normal_execution(self, mock_loadtxt): """ Test the normal behaviour of the function. """ # Set the mocked functions returned values bvecs = numpy.array([[0, 0, 0], [0, 0, 0], [1, 0, 0], [0, 1, 0]]).T bvals = numpy.array([0, 0, 1500, 150]) mock_loadtxt.side_effect = lambda x: { "dwi.bval": bvals, "dwi.bvec": bvecs }[x] # Test execution output_files = read_bvals_bvecs("dwi.bval", "dwi.bvec", min_bval=100.) self.assertTrue(numpy.allclose(output_files[0], bvals)) self.assertTrue(numpy.allclose(output_files[1], bvecs.T)) self.assertTrue(output_files[2] == 2) self.assertTrue(output_files[3] == 2)
def concatenate_volumes(nii_files, bvals_files, bvecs_files, outdir, axis=-1): """ Concatenate volumes of different nifti files. Parameters ---------- nii_files: array of str array containing the different nii files to concatenate. bvals_files: list of str path to the diffusion b-values files. bvecs_files: list of str path to the diffusion b-vectors files. outdir: str subject output directory. axis: int, default -1 the concatenation axis. Returns ------- dwi_file: str path to the concatenated nii files. bval_file: str path to the concatenated bval files. bvec_file: str path to the concatenated bvec files. """ # Concatenate volumes data = [] affines = [] for path in nii_files: im = nibabel.load(path) data.append(im.get_data()) affines.append(im.affine) concatenated_volumes = numpy.concatenate(data, axis=axis) # Check that affine are the same between volumes ref_affine = affines[0] for aff in affines: if not numpy.allclose(ref_affine, aff): raise ValueError("Different affines between DWI volumes: {0}" "...".format(nii_files)) # Read the bvals and bvecs bvals, bvecs, nb_shells, nb_nodiff = read_bvals_bvecs( bvals_files, bvecs_files, min_bval=200) if nb_nodiff > 1: nodiff_indexes = numpy.where(bvals <= 50)[0].tolist() b0_array = concatenated_volumes[..., nodiff_indexes[0]] b0_array.shape += (1, ) cpt_delete = 0 for i in nodiff_indexes: concatenated_volumes = numpy.delete( concatenated_volumes, i - cpt_delete, axis=3) bvals = numpy.delete(bvals, i - cpt_delete, axis=0) bvecs = numpy.delete(bvecs, i - cpt_delete, axis=0) cpt_delete += 1 concatenated_volumes = numpy.concatenate( (b0_array, concatenated_volumes), axis=3) bvals = numpy.concatenate((numpy.array([0]), bvals), axis=0) bvecs = numpy.concatenate((numpy.array([[0, 0, 0]]), bvecs), axis=0) # Save the results dwi_file = os.path.join(outdir, "dwi.nii.gz") bval_file = os.path.join(outdir, "dwi.bval") bvec_file = os.path.join(outdir, "dwi.bvec") concatenated_nii = nibabel.Nifti1Image(concatenated_volumes, ref_affine) nibabel.save(concatenated_nii, dwi_file) bvals.shape += (1, ) numpy.savetxt(bval_file, bvals.T, fmt="%f") numpy.savetxt(bvec_file, bvecs.T, fmt="%f") return dwi_file, bval_file, bvec_file
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 data_import_and_qspace_sampling( outdir, subject_id, dwis, bvals, bvecs, manufacturer, flipX=False, flipY=False, flipZ=False, invertX=True, invertY=False, invertZ=False, b0_magnitude=None, b0_phase=None, phase_axis="y", slice_axis="z", path_connectomist=DEFAULT_CONNECTOMIST_PATH): """ Wrapper to Connectomist's 'DWI & Q-space' tab. Parameters ---------- outdir: str path to Connectomist's output directory. subject_id: str the subject code in study. dwis: list of str path to Nifti diffusion-weighted datasets. bvals: list of str path to .bval files associated to the Nifti. bvecs: list of str path to .bvec files associated to the Nifti. manufacturer: str name of the manufacturer (e.g. "Siemens", "GE", "Philips" or "Bruker"). flipX, flipY, flipZ: bool (optional, default False) if True invert the x, y or z-axis of data. invertX: bool (optional, default True) if True invert x-axis of diffusion model. invertY, invertZ: bool (optional, default False) if True invert y or z-axis of diffusion model. b0_magnitude: str (optional, default None) path to the magnitude fieldmap (if fieldmap-based correction of susceptibility distortions is to be used). b0_phase: str (optional, default None) path to phase fieldmap (if fieldmap-based correction of susceptibility distortions is to be used). phase_axis: str (optional, default 'y') the acquistion phase axis 'x', 'y' or 'z'. slice_axis: str (optional, default 'z') the acquistion slice axis 'x', 'y' or 'z'. path_connectomist: str (optional) path to the Connectomist executable. Returns ------- outdir: str path to Connectomist's output directory. """ # Check input parameters for axis in (phase_axis, slice_axis): if axis not in AXIS: raise ValueError("Invalid axis '{0}'.".format(axis)) if len(set(map(len, (dwis, bvecs, bvals)))) != 1: raise ValueError("The DWIs, BVECs and BVALs must have the same number " "of elements.") # Create the Connectomist's import data directory dwis, bvals, bvecs, b0_magnitude, b0_phase = gather_and_format_input_files( outdir, dwis, bvals, bvecs, b0_magnitude, b0_phase) # Dict with all parameters for connectomist algorithm = "DWI-Data-Import-And-QSpace-Sampling" parameters_dict = { # Parameters are ordered as they appear in connectomist's GUI # --------------------------------------------------------------------- # Field: "Diffusion weighted-images" "fileNameDwi": ";".join(dwis), # "DW data" "sliceAxis": AXIS[slice_axis], # "Slice axis", default "Z-axis" "phaseAxis": AXIS[phase_axis], # "Phase axis", default "Y-axis" "manufacturer": None, # Subfield: "Advanced parameters" "flipAlongX": 2 if flipX else 0, # "Flip data along x" "flipAlongY": 2 if flipY else 0, "flipAlongZ": 2 if flipZ else 0, "numberOfDiscarded": 0, # "#discarded images at beginning" "numberOfT2": None, # "#T2" "numberOfRepetitions": 1, # "#repetitions" # --------------------------------------------------------------------- # Field: "Rotation of field of view", default is identity matrix "qSpaceTransform_xx": 1.0, "qSpaceTransform_xy": 0.0, "qSpaceTransform_xz": 0.0, "qSpaceTransform_yx": 0.0, "qSpaceTransform_yy": 1.0, "qSpaceTransform_yz": 0.0, "qSpaceTransform_zx": 0.0, "qSpaceTransform_zy": 0.0, "qSpaceTransform_zz": 1.0, # --------------------------------------------------------------------- # Field: "Q-space sampling" "qSpaceSamplingType": 4, # default "spherical single-shell custom" "qSpaceChoice5BValueFileNames": ";".join(bvals), "qSpaceChoice5BValueThreshold": 50, "qSpaceChoice5OrientationFileNames": ";".join(bvecs), # Apparently Connectomist uses 2 as True, and 0 as False. "invertXAxis": 2 if invertX else 0, "invertYAxis": 2 if invertY else 0, "invertZAxis": 2 if invertZ else 0, # In this field but not used/handled parameters "qSpaceChoice1MaximumBValue": 1000, # case Cartesian "qSpaceChoice2BValue": 1000, "qSpaceChoice3BValue": 1000, "qSpaceChoice4BValue": 1000, "qSpaceChoice6BValues": "", "qSpaceChoice7BValues": "", "qSpaceChoice8BValues": "", "qSpaceChoice9BValues": "", "qSpaceChoice10BValues": "", "qSpaceChoice11BValues": "", "qSpaceChoice1NumberOfSteps": 11, "qSpaceChoice2NumberOfOrientations": 6, "qSpaceChoice3NumberOfOrientations": 6, "qSpaceChoice4NumberOfOrientations": 6, "qSpaceChoice6NumberOfOrientations": 6, "qSpaceChoice7NumberOfOrientations": 6, "qSpaceChoice8NumberOfOrientations": 6, "qSpaceChoice10NumberOfOrientations": "", "qSpaceChoice11NumberOfOrientations": "", "qSpaceChoice13OrientationFileNames": "", # --------------------------------------------------------------------- # Field: micro structure config" "gradientCharacteristicsFileNames": "", # --------------------------------------------------------------------- # Field: "Work directory" "outputWorkDirectory": outdir, # --------------------------------------------------------------------- # unknown parameter "_subjectName": subject_id, } # Map the manufacturer name with Connectomist convention if manufacturer not in MANUFACTURERS: raise ConnectomistBadManufacturerNameError(manufacturer) parameters_dict["manufacturer"] = MANUFACTURERS[manufacturer] # Read bvals and bvecs bvalues, bvectors, nb_shells, nb_nodiff = read_bvals_bvecs(bvals, bvecs) # Update Connectomist step description parameters_dict["numberOfT2"] = nb_nodiff if nb_shells == 1: # Spherical single-shell custom parameters_dict["qSpaceSamplingType"] = 4 else: warnings.warn( "'{0}' shell model(s) not handled yet.".format(nb_shells)) # Arbitrary shell parameters_dict["qSpaceSamplingType"] = 12 parameters_dict["qSpaceChoice13BValueFileNames"] = ";".join(bvals) parameters_dict["qSpaceChoice13BValueThreshold"] = 50.0 parameters_dict["qSpaceChoice13OrientationFileNames"] = ";".join(bvecs) # Call with Connectomist process = ConnectomistWrapper(path_connectomist) parameter_file = ConnectomistWrapper.create_parameter_file( algorithm, parameters_dict, outdir) process(algorithm, parameter_file, outdir) # When there are multiple volume or when there are multiple t2 (nodif) # volumes, Connectomist merges them # rewrite bvec, bval file accordingly (remove extra T2 values) if nb_nodiff > 1: bval = os.path.join(outdir, "dwi.bval") dw_indexes = np.where(bvalues >= 100)[0] new_bvals = np.concatenate(([0], bvalues[dw_indexes])) np.savetxt(bval, new_bvals) bvec = os.path.join(outdir, "dwi.bvec") new_bvecs = np.concatenate( ([[0], [0], [0]], bvectors.T[:, dw_indexes]), axis=1) np.savetxt(bvec, new_bvecs) return 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): """ 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 data_import_and_qspace_sampling( outdir, subject_id, dwis, bvals, bvecs, manufacturer, flipX=False, flipY=False, flipZ=False, invertX=True, invertY=False, invertZ=False, b0_magnitude=None, b0_phase=None, phase_axis="y", slice_axis="z", path_connectomist=DEFAULT_CONNECTOMIST_PATH): """ Wrapper to Connectomist's 'DWI & Q-space' tab. Parameters ---------- outdir: str path to Connectomist's output directory. subject_id: str the subject code in study. dwis: list of str path to Nifti diffusion-weighted datasets. bvals: list of str path to .bval files associated to the Nifti. bvecs: list of str path to .bvec files associated to the Nifti. manufacturer: str name of the manufacturer (e.g. "Siemens", "GE", "Philips" or "Bruker"). flipX, flipY, flipZ: bool (optional, default False) if True invert the x, y or z-axis of data. invertX: bool (optional, default True) if True invert x-axis of diffusion model. invertY, invertZ: bool (optional, default False) if True invert y or z-axis of diffusion model. b0_magnitude: str (optional, default None) path to the magnitude fieldmap (if fieldmap-based correction of susceptibility distortions is to be used). b0_phase: str (optional, default None) path to phase fieldmap (if fieldmap-based correction of susceptibility distortions is to be used). phase_axis: str (optional, default 'y') the acquistion phase axis 'x', 'y' or 'z'. slice_axis: str (optional, default 'z') the acquistion slice axis 'x', 'y' or 'z'. path_connectomist: str (optional) path to the Connectomist executable. Returns ------- outdir: str path to Connectomist's output directory. """ # Check input parameters for axis in (phase_axis, slice_axis): if axis not in AXIS: raise ValueError("Invalid axis '{0}'.".format(axis)) if len(set(map(len, (dwis, bvecs, bvals)))) != 1: raise ValueError("The DWIs, BVECs and BVALs must have the same number " "of elements.") # Create the Connectomist's import data directory dwis, bvals, bvecs, b0_magnitude, b0_phase = gather_and_format_input_files( outdir, dwis, bvals, bvecs, b0_magnitude, b0_phase) # Dict with all parameters for connectomist algorithm = "DWI-Data-Import-And-QSpace-Sampling" parameters_dict = { # Parameters are ordered as they appear in connectomist's GUI # --------------------------------------------------------------------- # Field: "Diffusion weighted-images" "fileNameDwi": ";".join(dwis), # "DW data" "sliceAxis": AXIS[slice_axis], # "Slice axis", default "Z-axis" "phaseAxis": AXIS[phase_axis], # "Phase axis", default "Y-axis" "manufacturer": None, # Subfield: "Advanced parameters" "flipAlongX": 2 if flipX else 0, # "Flip data along x" "flipAlongY": 2 if flipY else 0, "flipAlongZ": 2 if flipZ else 0, "numberOfDiscarded": 0, # "#discarded images at beginning" "numberOfT2": None, # "#T2" "numberOfRepetitions": 1, # "#repetitions" # --------------------------------------------------------------------- # Field: "Rotation of field of view", default is identity matrix "qSpaceTransform_xx": 1.0, "qSpaceTransform_xy": 0.0, "qSpaceTransform_xz": 0.0, "qSpaceTransform_yx": 0.0, "qSpaceTransform_yy": 1.0, "qSpaceTransform_yz": 0.0, "qSpaceTransform_zx": 0.0, "qSpaceTransform_zy": 0.0, "qSpaceTransform_zz": 1.0, # --------------------------------------------------------------------- # Field: "Q-space sampling" "qSpaceSamplingType": 4, # default "spherical single-shell custom" "qSpaceChoice5BValueFileNames": ";".join(bvals), "qSpaceChoice5BValueThreshold": 50, "qSpaceChoice5OrientationFileNames": ";".join(bvecs), # Apparently Connectomist uses 2 as True, and 0 as False. "invertXAxis": 2 if invertX else 0, "invertYAxis": 2 if invertY else 0, "invertZAxis": 2 if invertZ else 0, # In this field but not used/handled parameters "qSpaceChoice1MaximumBValue": 1000, # case Cartesian "qSpaceChoice2BValue": 1000, "qSpaceChoice3BValue": 1000, "qSpaceChoice4BValue": 1000, "qSpaceChoice6BValues": "", "qSpaceChoice7BValues": "", "qSpaceChoice8BValues": "", "qSpaceChoice9BValues": "", "qSpaceChoice10BValues": "", "qSpaceChoice11BValues": "", "qSpaceChoice1NumberOfSteps": 11, "qSpaceChoice2NumberOfOrientations": 6, "qSpaceChoice3NumberOfOrientations": 6, "qSpaceChoice4NumberOfOrientations": 6, "qSpaceChoice6NumberOfOrientations": 6, "qSpaceChoice7NumberOfOrientations": 6, "qSpaceChoice8NumberOfOrientations": 6, "qSpaceChoice10NumberOfOrientations": "", "qSpaceChoice11NumberOfOrientations": "", "qSpaceChoice13OrientationFileNames": "", # --------------------------------------------------------------------- # Field: micro structure config" "gradientCharacteristicsFileNames": "", # --------------------------------------------------------------------- # Field: "Work directory" "outputWorkDirectory": outdir, # --------------------------------------------------------------------- # unknown parameter "_subjectName": subject_id, } # Map the manufacturer name with Connectomist convention if manufacturer not in MANUFACTURERS: raise ConnectomistBadManufacturerNameError(manufacturer) parameters_dict["manufacturer"] = MANUFACTURERS[manufacturer] # Read bvals and bvecs bvalues, bvectors, nb_shells, nb_nodiff = read_bvals_bvecs(bvals, bvecs) # Update Connectomist step description parameters_dict["numberOfT2"] = nb_nodiff if nb_shells == 1: # Spherical single-shell custom parameters_dict["qSpaceSamplingType"] = 4 else: warnings.warn( "'{0}' shell model(s) not handled yet.".format(nb_shells)) # Arbitrary shell parameters_dict["qSpaceSamplingType"] = 12 parameters_dict["qSpaceChoice13BValueFileNames"] = ";".join(bvals) parameters_dict["qSpaceChoice13BValueThreshold"] = 50.0 parameters_dict["qSpaceChoice13OrientationFileNames"] = ";".join(bvecs) # Call with Connectomist process = ConnectomistWrapper(path_connectomist) parameter_file = ConnectomistWrapper.create_parameter_file( algorithm, parameters_dict, outdir) process(algorithm, parameter_file, outdir) # When there are multiple volume or when there are multiple t2 (nodif) # volumes, Connectomist merges them # rewrite bvec, bval file accordingly (remove extra T2 values) if nb_nodiff > 1: bval = os.path.join(outdir, "dwi.bval") dw_indexes = np.where(bvalues >= 100)[0] new_bvals = np.concatenate(([0], bvalues[dw_indexes])) np.savetxt(bval, new_bvals) bvec = os.path.join(outdir, "dwi.bvec") new_bvecs = np.concatenate( ([[0], [0], [0]], bvectors.T[:, dw_indexes]), axis=1) np.savetxt(bvec, new_bvecs) return 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