def qc_connectogram(outdir, tracto_mask, fiber_density, txt_matrix, txt_matrix_normalized, txt_labels, subdir="qc"): """ Create 3 snapshots: - fiber density - raw connetivity matrix - normalized connectivity matrix 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. 'fiber_density' and 'txt_matrix' are outputs of probtrackx2. """ # 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(tracto_mask).get_shape()[2] fiber_density_snapshot = os.path.join(outdir, "fiber_density_map.pdf") plot_image(tracto_mask, overlay_file=fiber_density, snap_file=fiber_density_snapshot, name="fiber density map", overlay_cmap="cold_hot", cut_coords=nb_slices_in_z - 2) # connectogram snapshot connectogram_snapshot = plot_connectogram(outdir, txt_matrix, labels=txt_labels, transform=np.log1p) # Normalized by surface target area connectogram snapshot connectogram_norm_snapshot = \ plot_connectogram(outdir, txt_matrix_normalized, labels=txt_labels, transform=np.log1p, prefix="connectogram_normalized") return connectogram_snapshot, connectogram_norm_snapshot
def qc_connectogram(outdir, tracto_mask, proba_matrix, network_matrix, seed_masks, subdir="qc"): """ Function meant to help quality check (qc) the tractography and the connectogram computed by the probtrackx2_probabilist_tractography function. It creates snap shots to visualize the connectogram and the fiber density 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="tracto_mask" type="File" /> <input name="proba_matrix" type="File" /> <input name="network_matrix" type="File" /> <input name="seed_masks" type="List" content="File" /> <input name="subdir" type="Str" /> <output name="qc_dir" type="Directory" /> </unit> """ # 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(tracto_mask).get_shape()[2] fiber_density_png = os.path.join(outdir, "fiber_density_map.png") plot_image(tracto_mask, overlay_file = proba_matrix, snap_file = fiber_density_png, name = "fiber density map", overlay_cmap = "cold_hot", cut_coords = nb_slices_in_z/2) # Second connectogram snap shot as PNG with cortex region labels plot_connectogram(outdir, network_matrix, seed_masks, transform=np.log1p) # Return something for Capsul qc_dir = outdir return qc_dir
def qc_tracto_masks(outdir, tracto_mask, seed_mask, stop_mask, txt_roi_masks, subdir="qc"): """ Function meant to help quality check (qc) the masks created by the create_masks_for_tracto_seeding_*() functions. It creates snap shots to visualize the quality of the registration of the tractography 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. """ # 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.mkdir(outdir) nb_slices_in_z = nibabel.load(tracto_mask).get_shape()[2] # PDF snapshot basename = "tracto_mask_and_stop_mask" pdf_snapshot_1 = os.path.join(outdir, "%s.pdf" % basename) plot_image(tracto_mask, overlay_file=stop_mask, snap_file=pdf_snapshot_1, name=basename, cut_coords=nb_slices_in_z - 2) # PDF snapshot basename = "tracto_mask_and_seed_mask" pdf_snapshot_2 = os.path.join(outdir, "%s.pdf" % basename) plot_image(tracto_mask, overlay_file=seed_mask, snap_file=pdf_snapshot_2, name=basename, cut_coords=nb_slices_in_z - 2) return outdir
if not os.path.isfile(b0_file): extract_image( args.diffusion_file, index=b0_index, out_file=b0_file) # Get the qc output directory if args.graphics: qcdir = os.path.join(subjdir, "qc") if not os.path.isdir(qcdir): os.makedirs(qcdir) # create a pdf snap of the b0 image if args.graphics: snap_file = os.path.join(qcdir, "nodif.pdf") plot_image(b0_file, snap_file=snap_file, name="nodif") # generate a brain mask on the corrected b0 data b0_brain_file = os.path.join(subjdir, "nodif_brain") bet_files = glob.glob(b0_brain_file + "*") if len(bet_files) == 0: (output, mask_file, mesh_file, outline_file, inskull_mask_file, inskull_mesh_file, outskull_mask_file, outskull_mesh_file, outskin_mask_file, outskin_mesh_file, skull_mask_file) = bet2( b0_file, b0_brain_file, m=True, f=0.25, shfile=args.fslconfig) else:
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 bet2_nodif_brain(outdir, dwi, bval, subdir="bet2_nodif_brain", qc=True): """ Extract brain from b0 volume, i.e. in DW data for a volume where bvalue=0. Parameters ---------- outdir: Str, path to directory where to output. dwi: Str, path to DW data in which at least one volume was acquired with bvalue=0. bval: Str, path to .bval file associated to the DW data. subdir: Str, if you want the result files to be written in a subdirectory, specify the name, by default "bet2_nodif_brain". Return ------ nodif_brain: Str, path to the brain only volume. nodif_brain_mask: Str, path to the brain-only binary mask. <unit> <output name="nodif_brain" type="File" /> <output name="nodif_brain_mask" type="File" /> <input name="outdir" type="Directory" /> <input name="dwi" type="File" /> <input name="bval" type="File" /> <input name="subdir" type="Str" /> <input name="qc" type="Bool" /> </unit> """ if subdir: outdir = os.path.join(outdir, subdir) # Create outdir if it does not exist if not os.path.isdir(outdir): os.makedirs(outdir) # Get a/the volume with bvalue=0. nodif_volume = extract_nodif_volume(outdir, dwi, bval) # Set output path with desired prefix name output_prefix = os.path.join(outdir, "nodif_brain") # Run FSL bet2 bet2(nodif_volume, output_prefix, f=0.25, m=True) # Output paths nodif_brain = output_prefix + ".nii.gz" nodif_brain_mask = output_prefix + "_mask.nii.gz" # Check existence of resulting files if not (os.path.isfile(nodif_brain) and os.path.isfile(nodif_brain_mask)): raise Exception("bet2: output file(s) missing: %s or/and %s ." % (nodif_brain, nodif_brain_mask)) # If Quality Check, generate a PNG snapshot if qc: # Snap shot of brain-only contour on T2 image brain_contour_png = os.path.join(outdir, "nodif_brain_mask.png") plot_image(nodif_volume, contour_file=nodif_brain_mask, snap_file=brain_contour_png, name="nodif_brain_mask") return nodif_brain, nodif_brain_mask
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_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
For probabalistic tractography, we need to generate a mask within which we constrain tractography. We first select the first non-diffusion weighted volume of the DTI sequence and then use 'bet2' on this image with a fractional intensity threshold of 0.25 (this is generally a robust threshold to remove unwanted tissue from a non-diffusion weighted image) and the 'm' option that creates a binary 'nodif_brain_mask' image. """ bvals = numpy.loadtxt(bvals_file).tolist() b0_index = bvals.index(0) b0_file = os.path.join(outdir, "nodif.nii.gz") if not os.path.isfile(b0_file): extract_image(diffusion_file, index=b0_index, out_file=b0_file) snap_file = os.path.join(qcdir, "nodif.pdf") plot_image(b0_file, snap_file=snap_file, name="nodif") b0_brain_file = os.path.join(outdir, "nodif_brain") bet_files = glob.glob(b0_brain_file + "*") if len(bet_files) == 0: (output, mask_file, mesh_file, outline_file, inskull_mask_file, inskull_mesh_file, outskull_mask_file, outskull_mesh_file, outskin_mask_file, outskin_mesh_file, skull_mask_file) = bet2(b0_file, b0_brain_file, m=True, f=0.25) else: mask_file = sorted(bet_files)[0] if not os.path.isfile(mask_file): raise IOError("FileDoesNotExist: '{0}'.".format(mask_file)) snap_file = os.path.join(qcdir, "bet.pdf") plot_image(b0_file, contour_file=mask_file, snap_file=snap_file, name="bet")
intensity threshold of 0.25 (this is generally a robust threshold to remove unwanted tissue from a non-diffusion weighted image) and the 'm' option that creates a binary 'nodif_brain_mask' image. """ bvals = numpy.loadtxt(bvals_file).tolist() b0_index = bvals.index(0) b0_file = os.path.join(outdir, "nodif.nii.gz") if not os.path.isfile(b0_file): extract_image( diffusion_file, index=b0_index, out_file=b0_file) snap_file = os.path.join(qcdir, "nodif.pdf") plot_image(b0_file, snap_file=snap_file, name="nodif") b0_brain_file = os.path.join(outdir, "nodif_brain") bet_files = glob.glob(b0_brain_file + "*") if len(bet_files) == 0: (output, mask_file, mesh_file, outline_file, inskull_mask_file, inskull_mesh_file, outskull_mask_file, outskull_mesh_file, outskin_mask_file, outskin_mesh_file, skull_mask_file) = bet2( b0_file, b0_brain_file, m=True, f=0.25) else: mask_file = sorted(bet_files)[0]
def brainsuite_susceptibility_correction(outdir, dwi, bval, bvec, subject_id, phase_enc_dir, subjects_dir=None, qc_dir=None, bdp_nthread=4): """ Assuming the beginning of the preprocessing was done with Connectomist up to Eddy current and motion correction, we now want to make susceptbility distortion correction using Brainsuite. Parameters ---------- outdir: str Path to directory where to output. dwi: str Path to the preprocessed diffusion-weighted Nifti. bval: str Path to the bval file associated to the Nifti. bvec: str Path to the bvec file associated to the Nifti. subject_id: str Subject identifier used in Freesurfer. phase_enc_dir: str In plane phase encoding direction, "y", "y-", "x" or "x-". subjects_dir: str, default None If the Freesurfer $SUBJECTS_DIR environment variable is not set, or to bypass it, pass the path. qc_dir: str, default None Path to directory where to output snapshots for QC. By default in outdir. bdp_nthread: int, default 4 Number of threads for bdp (see bdp.sh --thread flag) """ # Freesurfer 'subjects_dir' has to be passed or set as environment variable subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir) # Check in plane phase encoding direction # The sign of the phase encoding direction has no impact in this strategy # e.g. for "+y" or "-y" use "y" possible_phase_enc_dirs = {"y", "x"} if phase_enc_dir not in possible_phase_enc_dirs: raise ValueError( "Bad argument 'phase_enc_dir': {}, should be in {}.".format( phase_enc_dir, possible_phase_enc_dirs)) # Set and check path to T1 brain-only volume from Freesurfer (mgz format) t1_brain_mgz = os.path.join(subjects_dir, subject_id, "mri/brain.mgz") if not os.path.isfile(t1_brain_mgz): raise Exception("Missing file: {}".format(t1_brain_mgz)) # Convert Freesurfer T1 to Nifti with reorientation to RAS (to be in the # same orientation as the diffusion data) t1_brain_RAS_nii = os.path.join(outdir, "t1_brain.nii.gz") cmd = [ "mri_convert", t1_brain_mgz, t1_brain_RAS_nii, "--out_orientation", "RAS" ] run_freesurfer_cmd(cmd, subjects_dir=subjects_dir) # Run bfc (bias correction: required by BrainSuite) t1_bfc = os.path.join(outdir, "t1_brain.bfc.nii.gz") cmd = ["bfc", "-i", t1_brain_RAS_nii, "-o", t1_bfc] subprocess.check_call(cmd) # Extract brain from the nodif volume with FSL bet2 nodif_brain = os.path.join(outdir, "nodif_brain.nii.gz") bet2(dwi, nodif_brain, f=0.25, m=False) # Run bdp.sh: registration + diffusion model cmd = [ "bdp.sh", t1_bfc, "--nii", dwi, "--bval", bval, "--bvec", bvec, "--dwi-mask", nodif_brain, "--dir=%s" % phase_enc_dir, "--threads=%i" % bdp_nthread ] subprocess.check_call(cmd) # Path to files of interest, created by BrainSuite bdp.sh dwi_wo_susceptibility = os.path.join(outdir, "t1_brain.dwi.RAS.correct.nii.gz") ############### # Quality check: create snapshots to visually assert the registration # quality if qc_dir is None: qc_dir = outdir # The snapshots won't contain all slices, half of them nb_slices_in_z = nibabel.load(nodif_brain).get_shape()[2] # Path to registered T1 t1_to_dif = os.path.join(outdir, "t1_brain.D_coord.nii.gz") # First png: T1 registered in diffusion with nodif edges t1_with_nodif_edges_png = os.path.join(qc_dir, "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(qc_dir, "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) return dwi_wo_susceptibility, bval, bvec
bvals = numpy.loadtxt(args.bvals_file).tolist() b0_index = bvals.index(0) b0_file = os.path.join(subjdir, "nodif.nii.gz") if not os.path.isfile(b0_file): extract_image(args.diffusion_file, index=b0_index, out_file=b0_file) # Get the qc output directory if args.graphics: qcdir = os.path.join(subjdir, "qc") if not os.path.isdir(qcdir): os.makedirs(qcdir) # create a pdf snap of the b0 image if args.graphics: snap_file = os.path.join(qcdir, "nodif.pdf") plot_image(b0_file, snap_file=snap_file, name="nodif") # generate a brain mask on the corrected b0 data b0_brain_file = os.path.join(subjdir, "nodif_brain") bet_files = glob.glob(b0_brain_file + "*") if len(bet_files) == 0: (output, mask_file, mesh_file, outline_file, inskull_mask_file, inskull_mesh_file, outskull_mask_file, outskull_mesh_file, outskin_mask_file, outskin_mesh_file, skull_mask_file) = bet2(b0_file, b0_brain_file, m=True, f=args.thresh, shfile=args.fslconfig) else: mask_file = sorted(bet_files)[0]
def brainsuite_susceptibility_correction(outdir, dwi, bval, bvec, subject_id, fs_subjects_dir = None, qc_dir = None, bdp_nthread = 8): """ Assuming the beginning of the preprocessing was done with Connectomist up to Eddy current and motion correction, we now want to make susceptbility distortion correction using Brainsuite. Parameters ---------- outdir: Str, path to directory where to output. dwi bval bvec subject_id: Str, subject identifier used in Freesurfer. fs_subjects_dir: If the Freesurfer $SUBJECTS_DIR environment variable is not set, or to bypass it, pass the path. qc_dir: Str, path to directory where to output snapshots for QC. bdp_nthread: Int, nb of threads for bdp (see bdp.sh --thread flag) """ # If Freesurfer SUBJECTS_DIR is not passed, it should be set as environment variable 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.") # Set and check path to T1 brain-only volume from Freesurfer (mgz format) t1_brain_mgz = os.path.join(fs_subjects_dir, subject_id, "mri/brain.mgz") if not os.path.isfile(t1_brain_mgz): raise Exception("Missing file: {}".format(t1_brain_mgz)) # Convert Freesurfer T1 to Nifti with reorientation to RAS (to be in the # same orientation as the diffusion data) t1_brain_RAS_nii = os.path.join(outdir, "t1_brain.nii.gz") cmd = ["mri_convert", t1_brain_mgz, t1_brain_RAS_nii, "--out_orientation", "RAS"] fsprocess = FSWrapper(cmd) fsprocess() # Run if fsprocess.exitcode != 0: raise FreeSurferRuntimeError(cmd[0], " ".join(cmd[1:])) # Run bfc (bias correction: required by BrainSuite) t1_bfc = os.path.join(outdir, "t1_brain.bfc.nii.gz") cmd = ["bfc", "-i", t1_brain_RAS_nii, "-o", t1_bfc] subprocess.check_call(cmd) # Extract brain from the nodif volume with FSL bet2 nodif_brain = os.path.join(outdir, "nodif_brain.nii.gz") bet2(dwi, nodif_brain, f=0.25, m=False) # Run bdp.sh: registration + diffusion model cmd = ["bdp.sh", t1_bfc, "--nii", dwi, "--bval", bval, "--bvec", bvec, "--dwi-mask", nodif_brain, "--threads=%i" % bdp_nthread] subprocess.check_call(cmd) # Path to files of interest, created by BrainSuite bdp.sh dwi_wo_susceptibility = os.path.join(outdir, "t1_brain.dwi.RAS.correct.nii.gz") ############### # Quality check: create snapshots to visually assert the registration quality if qc_dir is None: qc_dir = outdir # The snapshots won't contain all slices, half of them nb_slices_in_z = nibabel.load(nodif_brain).get_shape()[2] # Path to registered T1 t1_to_dif = os.path.join(outdir, "t1_brain.D_coord.nii.gz") # First png: T1 registered in diffusion with nodif edges t1_with_nodif_edges_png = os.path.join(qc_dir, "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(qc_dir, "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) return dwi_wo_susceptibility, bval, bvec
def qc_dif2anat_registration(outdir, nodif_brain, dif2anat_dat, subject_id, 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: - T1 brain registered in diffusion + contour of nodif brain volume. The snap shot is saved in <outdir>/<subdir>/"t1_to_diff.png". 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. """ # Freesurfer 'subjects_dir' has to be passed or set as environment variable subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir) # 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(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"] run_freesurfer_cmd(cmd) nb_slices_in_z = nibabel.load(nodif_brain).get_shape()[2] # PDF snapshots: registered T1 (in diffusion) + nodif edges pdf_t1_with_nodif_edges = os.path.join(outdir, "t1_with_nodif_edges.pdf") plot_image(t1_to_dif, edge_file=nodif_brain, snap_file=pdf_t1_with_nodif_edges, name="T1 in diffusion + edges of nodif", cut_coords=nb_slices_in_z - 2) # PDF snapshots: nodif brain + registered T1 edges pdf_nodif_with_t1_edges = os.path.join(outdir, "nodif_with_t1_edges.pdf") plot_image(nodif_brain, edge_file=t1_to_dif, snap_file=pdf_nodif_with_t1_edges, name="nodif + edges of registered T1", cut_coords=nb_slices_in_z - 2) return outdir
def brainsuite_susceptibility_correction(outdir, dwi, bval, bvec, subject_id, phase_enc_dir, subjects_dir=None, qc_dir=None, bdp_nthread=4): """ Assuming the beginning of the preprocessing was done with Connectomist up to Eddy current and motion correction, we now want to make susceptbility distortion correction using Brainsuite. Parameters ---------- outdir: str Path to directory where to output. dwi: str Path to the preprocessed diffusion-weighted Nifti. bval: str Path to the bval file associated to the Nifti. bvec: str Path to the bvec file associated to the Nifti. subject_id: str Subject identifier used in Freesurfer. phase_enc_dir: str In plane phase encoding direction, "y", "y-", "x" or "x-". subjects_dir: str, default None If the Freesurfer $SUBJECTS_DIR environment variable is not set, or to bypass it, pass the path. qc_dir: str, default None Path to directory where to output snapshots for QC. By default in outdir. bdp_nthread: int, default 4 Number of threads for bdp (see bdp.sh --thread flag) """ # Freesurfer 'subjects_dir' has to be passed or set as environment variable subjects_dir = get_or_check_freesurfer_subjects_dir(subjects_dir) # Check in plane phase encoding direction # The sign of the phase encoding direction has no impact in this strategy # e.g. for "+y" or "-y" use "y" possible_phase_enc_dirs = {"y", "x"} if phase_enc_dir not in possible_phase_enc_dirs: raise ValueError("Bad argument 'phase_enc_dir': {}, should be in {}." .format(phase_enc_dir, possible_phase_enc_dirs)) # Set and check path to T1 brain-only volume from Freesurfer (mgz format) t1_brain_mgz = os.path.join(subjects_dir, subject_id, "mri/brain.mgz") if not os.path.isfile(t1_brain_mgz): raise Exception("Missing file: {}".format(t1_brain_mgz)) # Convert Freesurfer T1 to Nifti with reorientation to RAS (to be in the # same orientation as the diffusion data) t1_brain_RAS_nii = os.path.join(outdir, "t1_brain.nii.gz") cmd = ["mri_convert", t1_brain_mgz, t1_brain_RAS_nii, "--out_orientation", "RAS"] run_freesurfer_cmd(cmd, subjects_dir=subjects_dir) # Run bfc (bias correction: required by BrainSuite) t1_bfc = os.path.join(outdir, "t1_brain.bfc.nii.gz") cmd = ["bfc", "-i", t1_brain_RAS_nii, "-o", t1_bfc] subprocess.check_call(cmd) # Extract brain from the nodif volume with FSL bet2 nodif_brain = os.path.join(outdir, "nodif_brain.nii.gz") bet2(dwi, nodif_brain, f=0.25, m=False) # Run bdp.sh: registration + diffusion model cmd = ["bdp.sh", t1_bfc, "--nii", dwi, "--bval", bval, "--bvec", bvec, "--dwi-mask", nodif_brain, "--dir=%s" % phase_enc_dir, "--threads=%i" % bdp_nthread] subprocess.check_call(cmd) # Path to files of interest, created by BrainSuite bdp.sh dwi_wo_susceptibility = os.path.join( outdir, "t1_brain.dwi.RAS.correct.nii.gz") ############### # Quality check: create snapshots to visually assert the registration # quality if qc_dir is None: qc_dir = outdir # The snapshots won't contain all slices, half of them nb_slices_in_z = nibabel.load(nodif_brain).get_shape()[2] # Path to registered T1 t1_to_dif = os.path.join(outdir, "t1_brain.D_coord.nii.gz") # First png: T1 registered in diffusion with nodif edges t1_with_nodif_edges_png = os.path.join(qc_dir, "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(qc_dir, "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) return dwi_wo_susceptibility, bval, bvec
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