Example #1
0
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
This process usually takes between 20-40 hours depending on the quality of
data.
"""

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.
Example #3
0
This process usually takes between 20-40 hours depending on the quality of
data.
"""

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.
Example #4
0
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)
Example #5
0
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
Example #6
0
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)
Example #7
0
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
Example #8
0
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