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. From our DTI data, we need to produce the following information:
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: * The mask of the non-diffusion-weighted image.
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