def qc_tractography_masks(outdir, nodif_brain, tracto_mask, seed_masks, subject_id, cortical_atlas = "Desikan", fs_subjects_dir = None, subdir = "qc"): """ Function meant to help quality check (qc) the masks created by the create_masks_for_tractography function. It creates snap shots to visualize the quality of the registration of the tractography mask (white matter) and seed masks in the diffusion space. The snap shots are saved in <outdir>/<subdir>/<snap shot>. By default <subdir> is "qc". To write in outdir directly, set subdir to anything that evaluates to False (empty string or None). Directories are automatically created if they don't exist. <unit> <input name="outdir" type="Directory" /> <input name="nodif_brain" type="File" /> <input name="tracto_mask" type="File" /> <input name="seed_masks" type="List" content="File" /> <input name="subject_id" type="Str" /> <input name="cortical_atlas" type="Str" /> <input name="fs_subjects_dir" type="Directory" /> <input name="subdir" type="Str" /> <output name="qc_dir" type="Directory" /> </unit> """ if fs_subjects_dir is None: if "SUBJECTS_DIR" in os.environ: fs_subjects_dir = os.environ["SUBJECTS_DIR"] else: raise ValueError("Missing <SUBJECTS_DIR>: set the $SUBJECTS_DIR " "environment variable for Freesurfer or pass it " "as an argument.") # If requested use a subdirectory in outdir if subdir: outdir = os.path.join(outdir, subdir) # If outdir does not exist, create it if not os.path.isdir(outdir): os.makedirs(outdir) nb_slices_in_z = nibabel.load(nodif_brain).get_shape()[2] # Snap shot for registration and white matter mask checking wm_mask_to_dif_png = os.path.join(outdir, "tracto_mask.png") plot_image(nodif_brain, overlay_file = tracto_mask, snap_file = wm_mask_to_dif_png, name = "White matter mask in diffusion", cut_coords = nb_slices_in_z/2) # Gif of white matter mask in diffusion animate_image(nodif_brain, overlay_file=tracto_mask, outdir=outdir, name="tracto_mask", cut_coords=nb_slices_in_z/2, clean=True) # Return something for Capsul qc_dir = outdir return qc_dir
def qc_profile(nodif_file, proba_file, proba_texture, ico_order, fsdir, sid, outdir, fsconfig, actor_ang=(0., 0., 0.)): """ Connectivity profile QC. Generates views of: - the superposition of the nodif image with tractography result volume. - the connected points on the cortical surface Resample cortical meshes if needed. Results output are available as gif and png. Parameters ---------- nodif_file: str (mandatory) file for probtrackx2 containing the no diffusion volume and associated space information. proba_file: str (mandatory) the protrackx2 output seeding probabilistic path volume. proba_texture: dict (mandatory) the FreeSurfer mri_vol2surf '.mgz' 'lh' and 'rh' textrue that contains the cortiacal connection strength. ico_order: int (mandatory) icosahedron order in [0, 7] that will be used to generate the cortical surface texture at a specific tessalation (the corresponding cortical surface can be resampled using the 'clindmri.segmentation.freesurfer.resample_cortical_surface' function). fsdir: str (mandatory) FreeSurfer subjects directory 'SUBJECTS_DIR'. sid: str (mandatory) FreeSurfer subject identifier. outdir: str (mandatory) The QC output directory. fsconfig: str (mandatory) the FreeSurfer '.sh' config file. actor_ang: 3-uplet (optinal, default (0, 0, 0)) the actor x, y, z position (in degrees). Returns ------- snaps: list of str two gifs images, one showing the connection profile as a texture on the cortical surface, the other a volumic representation of the deterministic tractography. """ import clindmri.plot.pvtk as pvtk from clindmri.plot.slicer import animate_image # Construct/check the subject directory subjectdir = os.path.join(fsdir, sid) if not os.path.isdir(subjectdir): raise ValueError( "'{0}' is not a FreeSurfer subject directory.".format(subjectdir)) # Check that the output QC directory exists if not os.path.isdir(outdir): os.makedirs(outdir) # Superpose the nodif and probabilistic tractography volumes proba_shape = nibabel.load(proba_file).shape snaps = [] snaps.append( animate_image(nodif_file, overlay_file=proba_file, clean=True, overlay_cmap="Spectral", cut_coords=proba_shape[2], outdir=outdir)) # Define a renderer ren = pvtk.ren() # For each hemisphere for hemi in ["lh", "rh"]: # Get the the white mesh on the desired icosphere meshfile = os.path.join(subjectdir, "convert", "{0}.white.{1}.native".format(hemi, ico_order)) if not os.path.isfile(meshfile): raise ValueError( "'{0}' is not a valid white mesh. Generate it through the " "'clindmri.scripts.freesurfer_conversion' script.".format( meshfile)) # Check texture has the expected extension, size texture_file = proba_texture[hemi] if not texture_file.endswith(".mgz"): raise ValueError("'{0}' is not a '.mgz' file. Format not " "supported.".format(texture_file)) profile_array = nibabel.load(texture_file).get_data() profile_dim = profile_array.ndim profile_shape = profile_array.shape if profile_dim != 3: raise ValueError( "Expected profile texture array of dimension 3 not " "'{0}'".format(profile_dim)) if (profile_shape[1] != 1) or (profile_shape[2] != 1): raise ValueError( "Expected profile texture array of shape (*, 1, 1) not " "'{0}'.".format(profile_shape)) # Flatten the profile texture array texture = profile_array.ravel() # Load the white mesh surface = TriSurface.load(meshfile) # Define a textured surface actor actor = pvtk.surface(surface.vertices, surface.triangles, texture) actor.RotateX(actor_ang[0]) actor.RotateY(actor_ang[1]) actor.RotateZ(actor_ang[2]) pvtk.add(ren, actor) # Create a animaton with the generated surface qcname = "profile_as_texture" snaps.extend( pvtk.record(ren, outdir, qcname, n_frames=36, az_ang=10, animate=True, delay=10)) return snaps
def qc_dif2anat_registration(outdir, nodif_brain, dif2anat_dat, subject_id, fs_subjects_dir = None, subdir = "qc"): """ Function meant to help quality check (qc) the registration between the diffusion and the anatomy (T1 from Freesurfer recon-all). It creates snap shots to help with the visualization: - T1 brain registered in diffusion + contour of nodif brain volume. The snap shot is saved in <outdir>/<subdir>/"t1_to_diff.pdf". By default <subdir> is "qc". To write in outdir directly, set subdir to anything that evaluates to False (empty string or None). Directories are automatically created if they don't exist. <unit> <input name="outdir" type="Directory" /> <input name="nodif_brain" type="File" /> <input name="dif2anat_dat" type="File" /> <input name="subject_id" type="Str" /> <input name="fs_subjects_dir" type="Directory" /> <input name="subdir" type="Str" /> <output name="qc_dir" type="Directory" /> </unit> """ if fs_subjects_dir is None: if "SUBJECTS_DIR" in os.environ: fs_subjects_dir = os.environ["SUBJECTS_DIR"] else: raise ValueError("Missing <SUBJECTS_DIR>: set the $SUBJECTS_DIR " "environment variable for Freesurfer or pass it " "as an argument.") # If requested use a subdirectory in outdir if subdir: outdir = os.path.join(outdir, subdir) # If outdir does not exist, create it if not os.path.isdir(outdir): os.makedirs(outdir) # Paths t1 = os.path.join(fs_subjects_dir, subject_id, "mri/brainmask.mgz") t1_to_dif = os.path.join(outdir, "t1_to_dif.nii.gz") # Project T1 in dif cmd = ["mri_vol2vol", "--mov", nodif_brain, "--targ", t1, "--inv", "--interp", "nearest", "--o", t1_to_dif, "--reg", dif2anat_dat, "--no-save-reg"] fsprocess = FSWrapper(cmd) fsprocess() # Run if fsprocess.exitcode != 0: raise FreeSurferRuntimeError(cmd[0], " ".join(cmd[1:])) nb_slices_in_z = nibabel.load(nodif_brain).get_shape()[2] # Gif of the T1 in diffusion space with diffusion edges animate_image(t1_to_dif, edge_file=nodif_brain, outdir=outdir, name="t1_to_dif", cut_coords=nb_slices_in_z/2, clean=True) # First png: T1 registered in diffusion with nodif edges t1_with_nodif_edges_png = os.path.join(outdir, "t1_with_nodif_edges.png") plot_image(t1_to_dif, edge_file = nodif_brain, snap_file = t1_with_nodif_edges_png, name = "T1 in diffusion + edges of nodif", cut_coords = nb_slices_in_z/2) # Second png: nodif with edges of T1 registered in diffusion nodif_with_t1_edges_png = os.path.join(outdir, "nodif_with_t1_edges.png") plot_image(nodif_brain, edge_file = t1_to_dif, snap_file = nodif_with_t1_edges_png, name = "nodif + edges of registered T1", cut_coords = nb_slices_in_z/2) # # Third png: nodif with WM edges (from T1 segmentation) # nodif_with_wm_edges_png = os.path.join(outdir, "nodif_with_wm_edges.png") # plot_image(nodif_brain, # edge_file = t1_to_dif, # snap_file = nodif_with_t1_edges_png, # name = "nodif + edges of registered T1", # cut_coords = nb_slices_in_z/2) # Return something for Capsul qc_dir = outdir return qc_dir
def qc_profile(nodif_file, proba_file, proba_texture, ico_order, fsdir, sid, outdir, fsconfig, actor_ang=(0., 0., 0.)): """ Connectivity profile QC. Generates views of: - the superposition of the nodif image with tractography result volume. - the connected points on the cortical surface Resample cortical meshes if needed. Results output are available as gif and png. Parameters ---------- nodif_file: str (mandatory) file for probtrackx2 containing the no diffusion volume and associated space information. proba_file: str (mandatory) the protrackx2 output seeding probabilistic path volume. proba_texture: dict (mandatory) the FreeSurfer mri_vol2surf '.mgz' 'lh' and 'rh' textrue that contains the cortiacal connection strength. ico_order: int (mandatory) icosahedron order in [0, 7] that will be used to generate the cortical surface texture at a specific tessalation (the corresponding cortical surface can be resampled using the 'clindmri.segmentation.freesurfer.resample_cortical_surface' function). fsdir: str (mandatory) FreeSurfer subjects directory 'SUBJECTS_DIR'. sid: str (mandatory) FreeSurfer subject identifier. outdir: str (mandatory) The QC output directory. fsconfig: str (mandatory) the FreeSurfer '.sh' config file. actor_ang: 3-uplet (optinal, default (0, 0, 0)) the actor x, y, z position (in degrees). Returns ------- snaps: list of str two gifs images, one showing the connection profile as a texture on the cortical surface, the other a volumic representation of the deterministic tractography. """ import clindmri.plot.pvtk as pvtk from clindmri.plot.slicer import animate_image # Construct/check the subject directory subjectdir = os.path.join(fsdir, sid) if not os.path.isdir(subjectdir): raise ValueError( "'{0}' is not a FreeSurfer subject directory.".format(subjectdir)) # Check that the output QC directory exists if not os.path.isdir(outdir): os.makedirs(outdir) # Superpose the nodif and probabilistic tractography volumes proba_shape = nibabel.load(proba_file).shape snaps = [] snaps.append( animate_image(nodif_file, overlay_file=proba_file, clean=True, overlay_cmap="Spectral", cut_coords=proba_shape[2], outdir=outdir)) # Define a renderer ren = pvtk.ren() # For each hemisphere for hemi in ["lh", "rh"]: # Get the the white mesh on the desired icosphere meshfile = os.path.join( subjectdir, "convert", "{0}.white.{1}.native".format( hemi, ico_order)) if not os.path.isfile(meshfile): raise ValueError( "'{0}' is not a valid white mesh. Generate it through the " "'clindmri.scripts.freesurfer_conversion' script.".format( meshfile)) # Check texture has the expected extension, size texture_file = proba_texture[hemi] if not texture_file.endswith(".mgz"): raise ValueError("'{0}' is not a '.mgz' file. Format not " "supported.".format(texture_file)) profile_array = nibabel.load(texture_file).get_data() profile_dim = profile_array.ndim profile_shape = profile_array.shape if profile_dim != 3: raise ValueError( "Expected profile texture array of dimension 3 not " "'{0}'".format(profile_dim)) if (profile_shape[1] != 1) or (profile_shape[2] != 1): raise ValueError( "Expected profile texture array of shape (*, 1, 1) not " "'{0}'.".format(profile_shape)) # Flatten the profile texture array texture = profile_array.ravel() # Load the white mesh surface = TriSurface.load(meshfile) # Define a textured surface actor actor = pvtk.surface(surface.vertices, surface.triangles, texture) actor.RotateX(actor_ang[0]) actor.RotateY(actor_ang[1]) actor.RotateZ(actor_ang[2]) pvtk.add(ren, actor) # Create a animaton with the generated surface qcname = "profile_as_texture" snaps.extend( pvtk.record(ren, outdir, qcname, n_frames=36, az_ang=10, animate=True, delay=10)) return snaps