def qc(t1files, wmfiles, asegfiles, whitefiles, pialfiles, annotfiles, actor_ang=[0., 0., 0.], output_directory=None, fsconfig="/i2bm/local/freesurfer/SetUpFreeSurfer.sh"): """ Compute some quality check plots on the converted FrreSurfer outputs. The subjecy id in the input files must appear in the -3 position: xxx/subject_id/convert/t1.nii.gz Steps: * t1-images overlays * 3d surface segmentation snaps * t1-surfaces overlays actor_ang: float (optional, default 0) the actor rotation in the z direction. <unit> <input name="t1files" type="List_File" description="The t1 subject files."/> <input name="wmfiles" type="List_File" description="The white matter subject files."/> <input name="asegfiles" type="List_File" description="The subcortical segmentation subject files."/> <input name="output_directory" type="Directory" description="The conversion destination folder."/> <input name="whitefiles" type="List_File" description="The subject cortex surfaces."/> <input name="pialfiles" type="List_File" description="The subject pial surfaces."/> <input name="annotfiles" type="List_File" description="The pial/white surface annotations."/> <input name="actor_ang" type="List_Float" description="The actor x, y, z position (in degrees)."/> <input name="fsconfig" type="File" description="The freesurfer configuration batch."/> <output name="qcfiles" type="List_File" description="The quality check snaps."/> </unit> """ import clindmri.plot.pvtk as pvtk from clindmri.plot.slicer import plot_image # Create a t1 subject map t1map = {} for fname in t1files: subject_id = fname.split("/")[-3] if subject_id in t1map: raise Exception("Can't map two t1 for subject '{0}'" ".".format(subject_id)) t1map[subject_id] = fname # Create the output list that will contain all the qc files qcfiles = [] # Construct the t1-surfaces overlays and the 3d surface segmentation snaps ren = pvtk.ren() for name, files in [("white", whitefiles), ("pial", pialfiles)]: for fname in files: # Get the t1 reference image subject_id = fname.split("/")[-3] t1file = t1map[subject_id] t1_image = nibabel.load(t1file) # Get the qc output directory qcdir = os.path.join(os.path.dirname(fname), "qc") qcname = os.path.basename(fname) if not os.path.isdir(qcdir): os.makedirs(qcdir) # Get the triangular mesh basename = os.path.basename(fname).replace( name, "aparc.annot").replace(".native", "") annotfile = os.path.join(os.path.dirname(fname), basename) if annotfile not in annotfiles: raise ValueError( "Annotation file '{0}' can't be found.".format(annotfile)) surface = TriSurface.load(fname, annotfile=annotfile) # Construct the surfaces binarized volume binarizedfile = os.path.join(qcdir, qcname + ".nii.gz") overlay = numpy.zeros(t1_image.shape, dtype=numpy.uint) indices = numpy.round(surface.vertices).astype(int).T indices[0, numpy.where(indices[0] >= t1_image.shape[0])] = 0 indices[1, numpy.where(indices[1] >= t1_image.shape[1])] = 0 indices[2, numpy.where(indices[2] >= t1_image.shape[2])] = 0 overlay[indices.tolist()] = 1 overlay_image = nibabel.Nifti1Image(overlay, t1_image.get_affine()) nibabel.save(overlay_image, binarizedfile) snap_file = os.path.join(qcdir, qcname + ".png") plot_image(t1file, overlay_file=binarizedfile, snap_file=snap_file, name=qcname, overlay_cmap="cold_hot") qcfiles.append(snap_file) # Create a vtk surface actor of the cortex surface with the # associated labels ctab = [item["color"] for _, item in surface.metadata.items()] actor = pvtk.surface( surface.vertices, surface.triangles, surface.labels, ctab) actor.RotateX(actor_ang[0]) actor.RotateY(actor_ang[1]) actor.RotateZ(actor_ang[2]) # Create a 3d surface segmentation snap pvtk.add(ren, actor) snaps = pvtk.record(ren, qcdir, qcname, n_frames=36, az_ang=10, animate=True, delay=50) qcfiles.append(snaps[0]) snaps = pvtk.record(ren, qcdir, qcname + ".3d", n_frames=1) qcfiles.append(snaps[0]) pvtk.clear(ren) # Get the FreeSurfer lookup table fs_lut_names, fs_lut_colors = parse_fs_lut(os.path.join( os.path.dirname(fsconfig), "FreeSurferColorLUT.txt")) cmap = [] nb_values = numpy.asarray(fs_lut_colors.keys()).max() cmap = numpy.zeros((nb_values, 4), dtype=numpy.single) for key, color in fs_lut_colors.items(): if key > 0: cmap[key - 1, :3] = color cmap[:, 3] = 200. cmap /= 255. # Compute t1-images overlays for name, files in [("aseg", asegfiles), ("wm", wmfiles)]: for fname in files: # Get the t1 reference image subject_id = fname.split("/")[-3] t1file = t1map[subject_id] t1_image = nibabel.load(t1file) # Get the qc output directory qcdir = os.path.join(os.path.dirname(fname), "qc") if not os.path.isdir(qcdir): os.makedirs(qcdir) # Troncate the color map based on the label max array = nibabel.load(fname).get_data() order = sorted(set(array.flatten())) ccmap = cmap[order[1]: order[-1] + 1] # Overlay the current image with the t1 image qcname = "t1-{0}".format(name) snap_file = os.path.join(qcdir, qcname + ".png") plot_image(t1file, overlay_file=fname, snap_file=snap_file, name=qcname, overlay_cmap=ccmap, cut_coords=(0, 0, 0)) qcfiles.append(snap_file) return qcfiles
""" if fsdir is None: raise NotImplementedError() # recon-all -s 0001 -i T1.nii.gz if use_vtk: physical_to_index = numpy.linalg.inv(nibabel.load(t1_file).get_affine()) hemi_surfaces = read_cortex_surface_segmentation(fsdir, physical_to_index) ren = pvtk.ren() for hemi in ["lh", "rh"]: surface = hemi_surfaces[hemi] ctab = [item["color"] for _, item in surface.metadata.items()] actor = pvtk.surface(surface.vertices, surface.triangles, surface.labels, ctab) pvtk.add(ren, actor) pvtk.record(ren, qcdir, hemi + "_white") pvtk.clear(ren) actor = pvtk.surface(surface.inflated_vertices, surface.triangles, surface.labels, ctab) pvtk.add(ren, actor) pvtk.record(ren, qcdir, hemi + "_inflated") pvtk.clear(ren) """ Diffusion Processing ==================== At this point we have a motion- & artifact-corrected image, the corrected gradient table and a mask of the non-diffusion-weighted image.
""" if fsdir is None: raise NotImplementedError() # recon-all -s 0001 -i T1.nii.gz if use_vtk: physical_to_index = numpy.linalg.inv(nibabel.load(t1_file).get_affine()) hemi_surfaces = read_cortex_surface_segmentation(fsdir, physical_to_index) ren = pvtk.ren() for hemi in ["lh", "rh"]: surface = hemi_surfaces[hemi] ctab = [item["color"] for _, item in surface.metadata.items()] actor = pvtk.surface(surface.vertices, surface.triangles, surface.labels, ctab) pvtk.add(ren, actor) pvtk.record(ren, qcdir, hemi + "_white") pvtk.clear(ren) actor = pvtk.surface(surface.inflated_vertices, surface.triangles, surface.labels, ctab) pvtk.add(ren, actor) pvtk.record(ren, qcdir, hemi + "_inflated") pvtk.clear(ren) """ Diffusion Processing ==================== At this point we have a motion- & artifact-corrected image, the corrected gradient table and a mask of the non-diffusion-weighted image. From our DTI data, we need to produce the following information:
def display_folds(folds_file, labels, weights, white_file=None, interactive=True, snap=False, animate=False, outdir=None, name="folds", actor_ang=(0., 0., 0.)): """ Display the folds computed by morphologist. The scene supports one feature activated via the keystroke: * 'p': Pick the data at the current mouse point. This will pop-up a window with information on the current pick (ie. the fold name). Parameters ---------- folds_file: str( mandatory) the folds '.gii' file. labels: dict (mandatory) a mapping between a mesh id and its label. weights: dict (mandatory) a mapping between a mesh label and its wheight in [0, 1]. white_file: str (optional, default None) if specified the white surface will be displayed. interactive: bool (optional, default True) if True display the renderer. snap: bool (optional, default False) if True create a snap of the scene: need a valid outdir. animate: bool (optional, default False) if True create a gif 360 degrees animation of the scene: need a valid outdir. outdir: str (optional, default None) an existing directory. name: str (optional, default 'folds') the basename of the generated files. actor_ang: 3-uplet (optinal, default (0, 0, 0)) the actors x, y, z position (in degrees). """ # Load the folds file image = gio.read(folds_file) nb_of_surfs = len(image.darrays) if nb_of_surfs % 2 != 0: raise ValueError("Need an odd number of arrays (vertices, triangles).") # Create an actor for each fold ren = pvtk.ren() ren.SetBackground(1, 1, 1) for vertindex in range(0, nb_of_surfs, 2): vectices = image.darrays[vertindex].data triangles = image.darrays[vertindex + 1].data labelindex = image.darrays[vertindex].get_metadata()["Timestep"] if labelindex != image.darrays[vertindex + 1].get_metadata()[ "Timestep"]: raise ValueError("Gifti arrays '{0}' and '{1}' do not share the " "same label.".format(vertindex, vertindex + 1)) labelindex = int(labelindex) if labelindex in labels: label = labels[labelindex] if label in weights: weight = weights[label] * 256. else: weight = 0 else: label = "NC" weight = 0 surf = TriSurface(vectices, triangles, labels=None) actor = pvtk.surface(surf.vertices, surf.triangles, surf.labels + weight) actor.label = label actor.RotateX(actor_ang[0]) actor.RotateY(actor_ang[1]) actor.RotateZ(actor_ang[2]) pvtk.add(ren, actor) # Add the white surface if specified if white_file is not None: image = gio.read(white_file) nb_of_surfs = len(image.darrays) if nb_of_surfs != 2: raise ValueError("'{0}' does not a contain a valid white " "mesh.".format(white_file)) vectices = image.darrays[0].data triangles = image.darrays[1].data surf = TriSurface(vectices, triangles, labels=None) actor = pvtk.surface(surf.vertices, surf.triangles, surf.labels, opacity=1, set_lut=False) actor.label = "white" actor.RotateX(actor_ang[0]) actor.RotateY(actor_ang[1]) actor.RotateZ(actor_ang[2]) pvtk.add(ren, actor) # Show the renderer if interactive: actor = pvtk.text("!!!!", font_size=15, position=(10, 10), is_visible=False) pvtk.add(ren, actor) obs = LabelsOnPick(actor, static_position=True, to_keep_actors=["white"]) pvtk.show(ren, title="morphologist folds", observers=[obs]) # Create a snap if snap: if not os.path.isdir(outdir): raise ValueError("'{0}' is not a valid directory.".format(outdir)) pvtk.record(ren, outdir, name, n_frames=1) # Create an animation if animate: if not os.path.isdir(outdir): raise ValueError("'{0}' is not a valid directory.".format(outdir)) pvtk.record(ren, outdir, name, n_frames=36, az_ang=10, animate=True, delay=25)
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 display_folds(folds_file, labels, weights, white_file=None, interactive=True, snap=False, animate=False, outdir=None, name="folds", actor_ang=(0., 0., 0.)): """ Display the folds computed by morphologist. The scene supports one feature activated via the keystroke: * 'p': Pick the data at the current mouse point. This will pop-up a window with information on the current pick (ie. the fold name). Parameters ---------- folds_file: str( mandatory) the folds '.gii' file. labels: dict (mandatory) a mapping between a mesh id and its label. weights: dict (mandatory) a mapping between a mesh label and its wheight in [0, 1]. white_file: str (optional, default None) if specified the white surface will be displayed. interactive: bool (optional, default True) if True display the renderer. snap: bool (optional, default False) if True create a snap of the scene: need a valid outdir. animate: bool (optional, default False) if True create a gif 360 degrees animation of the scene: need a valid outdir. outdir: str (optional, default None) an existing directory. name: str (optional, default 'folds') the basename of the generated files. actor_ang: 3-uplet (optinal, default (0, 0, 0)) the actors x, y, z position (in degrees). """ # Load the folds file image = gio.read(folds_file) nb_of_surfs = len(image.darrays) if nb_of_surfs % 2 != 0: raise ValueError("Need an odd number of arrays (vertices, triangles).") # Create an actor for each fold ren = pvtk.ren() ren.SetBackground(1, 1, 1) for vertindex in range(0, nb_of_surfs, 2): vectices = image.darrays[vertindex].data triangles = image.darrays[vertindex + 1].data labelindex = image.darrays[vertindex].get_metadata()["Timestep"] if labelindex != image.darrays[vertindex + 1].get_metadata()["Timestep"]: raise ValueError("Gifti arrays '{0}' and '{1}' do not share the " "same label.".format(vertindex, vertindex + 1)) labelindex = int(labelindex) if labelindex in labels: label = labels[labelindex] if label in weights: weight = weights[label] * 256. else: weight = 0 else: label = "NC" weight = 0 surf = TriSurface(vectices, triangles, labels=None) actor = pvtk.surface(surf.vertices, surf.triangles, surf.labels + weight) actor.label = label actor.RotateX(actor_ang[0]) actor.RotateY(actor_ang[1]) actor.RotateZ(actor_ang[2]) pvtk.add(ren, actor) # Add the white surface if specified if white_file is not None: image = gio.read(white_file) nb_of_surfs = len(image.darrays) if nb_of_surfs != 2: raise ValueError("'{0}' does not a contain a valid white " "mesh.".format(white_file)) vectices = image.darrays[0].data triangles = image.darrays[1].data surf = TriSurface(vectices, triangles, labels=None) actor = pvtk.surface(surf.vertices, surf.triangles, surf.labels, opacity=1, set_lut=False) actor.label = "white" actor.RotateX(actor_ang[0]) actor.RotateY(actor_ang[1]) actor.RotateZ(actor_ang[2]) pvtk.add(ren, actor) # Show the renderer if interactive: actor = pvtk.text("!!!!", font_size=15, position=(10, 10), is_visible=False) pvtk.add(ren, actor) obs = LabelsOnPick(actor, static_position=True, to_keep_actors=["white"]) pvtk.show(ren, title="morphologist folds", observers=[obs]) # Create a snap if snap: if not os.path.isdir(outdir): raise ValueError("'{0}' is not a valid directory.".format(outdir)) pvtk.record(ren, outdir, name, n_frames=1) # Create an animation if animate: if not os.path.isdir(outdir): raise ValueError("'{0}' is not a valid directory.".format(outdir)) pvtk.record(ren, outdir, name, n_frames=36, az_ang=10, animate=True, delay=25)
def qc(t1files, wmfiles, asegfiles, whitefiles, pialfiles, annotfiles, actor_ang=[0., 0., 0.], output_directory=None, fsconfig="/i2bm/local/freesurfer/SetUpFreeSurfer.sh"): """ Compute some quality check plots on the converted FrreSurfer outputs. The subjecy id in the input files must appear in the -3 position: xxx/subject_id/convert/t1.nii.gz Steps: * t1-images overlays * 3d surface segmentation snaps * t1-surfaces overlays actor_ang: float (optional, default 0) the actor rotation in the z direction. <unit> <input name="t1files" type="List_File" description="The t1 subject files."/> <input name="wmfiles" type="List_File" description="The white matter subject files."/> <input name="asegfiles" type="List_File" description="The subcortical segmentation subject files."/> <input name="output_directory" type="Directory" description="The conversion destination folder."/> <input name="whitefiles" type="List_File" description="The subject cortex surfaces."/> <input name="pialfiles" type="List_File" description="The subject pial surfaces."/> <input name="annotfiles" type="List_File" description="The pial/white surface annotations."/> <input name="actor_ang" type="List_Float" description="The actor x, y, z position (in degrees)."/> <input name="fsconfig" type="File" description="The freesurfer configuration batch."/> <output name="qcfiles" type="List_File" description="The quality check snaps."/> </unit> """ import clindmri.plot.pvtk as pvtk from clindmri.plot.slicer import plot_image # Create a t1 subject map t1map = {} for fname in t1files: subject_id = fname.split("/")[-3] if subject_id in t1map: raise Exception("Can't map two t1 for subject '{0}'" ".".format(subject_id)) t1map[subject_id] = fname # Create the output list that will contain all the qc files qcfiles = [] # Construct the t1-surfaces overlays and the 3d surface segmentation snaps ren = pvtk.ren() for name, files in [("white", whitefiles), ("pial", pialfiles)]: for fname in files: # Get the t1 reference image subject_id = fname.split("/")[-3] t1file = t1map[subject_id] t1_image = nibabel.load(t1file) # Get the qc output directory qcdir = os.path.join(os.path.dirname(fname), "qc") qcname = os.path.basename(fname) if not os.path.isdir(qcdir): os.makedirs(qcdir) # Get the triangular mesh basename = os.path.basename(fname).replace(name, "aparc.annot").replace( ".native", "") annotfile = os.path.join(os.path.dirname(fname), basename) if annotfile not in annotfiles: raise ValueError( "Annotation file '{0}' can't be found.".format(annotfile)) surface = TriSurface.load(fname, annotfile=annotfile) # Construct the surfaces binarized volume binarizedfile = os.path.join(qcdir, qcname + ".nii.gz") overlay = numpy.zeros(t1_image.shape, dtype=numpy.uint) indices = numpy.round(surface.vertices).astype(int).T indices[0, numpy.where(indices[0] >= t1_image.shape[0])] = 0 indices[1, numpy.where(indices[1] >= t1_image.shape[1])] = 0 indices[2, numpy.where(indices[2] >= t1_image.shape[2])] = 0 overlay[indices.tolist()] = 1 overlay_image = nibabel.Nifti1Image(overlay, t1_image.get_affine()) nibabel.save(overlay_image, binarizedfile) snap_file = os.path.join(qcdir, qcname + ".png") plot_image(t1file, overlay_file=binarizedfile, snap_file=snap_file, name=qcname, overlay_cmap="cold_hot") qcfiles.append(snap_file) # Create a vtk surface actor of the cortex surface with the # associated labels ctab = [item["color"] for _, item in surface.metadata.items()] actor = pvtk.surface(surface.vertices, surface.triangles, surface.labels, ctab) actor.RotateX(actor_ang[0]) actor.RotateY(actor_ang[1]) actor.RotateZ(actor_ang[2]) # Create a 3d surface segmentation snap pvtk.add(ren, actor) snaps = pvtk.record(ren, qcdir, qcname, n_frames=36, az_ang=10, animate=True, delay=50) qcfiles.append(snaps[0]) snaps = pvtk.record(ren, qcdir, qcname + ".3d", n_frames=1) qcfiles.append(snaps[0]) pvtk.clear(ren) # Get the FreeSurfer lookup table fs_lut_names, fs_lut_colors = parse_fs_lut( os.path.join(os.path.dirname(fsconfig), "FreeSurferColorLUT.txt")) cmap = [] nb_values = numpy.asarray(fs_lut_colors.keys()).max() cmap = numpy.zeros((nb_values, 4), dtype=numpy.single) for key, color in fs_lut_colors.items(): if key > 0: cmap[key - 1, :3] = color cmap[:, 3] = 200. cmap /= 255. # Compute t1-images overlays for name, files in [("aseg", asegfiles), ("wm", wmfiles)]: for fname in files: # Get the t1 reference image subject_id = fname.split("/")[-3] t1file = t1map[subject_id] t1_image = nibabel.load(t1file) # Get the qc output directory qcdir = os.path.join(os.path.dirname(fname), "qc") if not os.path.isdir(qcdir): os.makedirs(qcdir) # Troncate the color map based on the label max array = nibabel.load(fname).get_data() order = sorted(set(array.flatten())) ccmap = cmap[order[1]:order[-1] + 1] # Overlay the current image with the t1 image qcname = "t1-{0}".format(name) snap_file = os.path.join(qcdir, qcname + ".png") plot_image(t1file, overlay_file=fname, snap_file=snap_file, name=qcname, overlay_cmap=ccmap, cut_coords=(0, 0, 0)) qcfiles.append(snap_file) return qcfiles
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