コード例 #1
0
ファイル: filetools.py プロジェクト: LisaPerus/pyconnectome
def load_folds(folds_file, graph_file=None):
    """ Load morphologist folds and associated labels.

    Parameters
    ----------
    folds_file: str( mandatory)
        the folds '.gii' file.
    graph_file: str (optional, default None)
        the path to a morphologist '.arg' graph file.

    Returns
    -------
    folds: dict with TriSurface
        all the loaded folds. The fold names are stored in the metadata.
    """
    # Load the labels
    if graph_file is not None:
        labels = parse_graph(graph_file)
    else:
        labels = {}

    # Load folds
    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).")
    folds = {}
    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]
        else:
            label = "NC"
        metadata = {"fold_name": label}
        surf = TriSurface(vectices, triangles, labels=None, metadata=metadata)
        folds[labelindex] = surf

    return folds
コード例 #2
0
ファイル: surfconvs.py プロジェクト: neurospin/pyfreesurfer
def surf_convert(
        fsdir,
        t1files,
        surffiles,
        sidpos=-3,
        rm_orig=False,
        fsconfig=DEFAULT_FREESURFER_PATH):
    """ Export FreeSurfer surfaces to the native space.

    Note that all the returned vetices are given in the index coordinate
    system.

    The subject id in the t1 and surf files must appear in the 'sidpos'
    position. For the default value '-3', the T1 path might look like
    'xxx/subject_id/convert/t1.nii.gz'

    Parameters
    ----------
    fsdir: str (mandatory)
        The FreeSurfer working directory with all the subjects.
    t1files: str (mandatory)
        The t1 nifti files.
    surffiles:
        The surfaces to be converted.
    sidpos: int (optional, default -3)
        The subject identifier position in the surface and T1 files.
    rm_orig: bool (optional)
        If True remove the input surfaces.
    fsconfig: str (optional)
        The FreeSurfer configuration batch.

    Returns
    -------
    csurffiles:
        The converted surfaces in the native space indexed coordinates.
    """
    # Check input parameters
    for path in t1files + surffiles:
        if not os.path.isfile(path):
            raise ValueError("'{0}' is not a valid file.".format(path))
    if not os.path.isdir(fsdir):
        raise ValueError("'{0}' is not a valid directory.".format(fsdir))

    # Create a t1 subject map
    t1map = {}
    for fname in t1files:
        subject_id = fname.split(os.path.sep)[sidpos]
        if subject_id in t1map:
            raise ValueError("Can't map two t1 for subject "
                             "'{0}'.".format(subject_id))
        t1map[subject_id] = fname

    # Convert all the surfaces
    csurffiles = []
    for fname in surffiles:

        # Get the t1 reference image
        subject_id = fname.split(os.path.sep)[sidpos]
        t1file = t1map[subject_id]
        t1_image = nibabel.load(t1file)

        # Compute the conformed space to the native anatomical deformation
        asegfile = os.path.join(fsdir, subject_id, "mri", "aseg.mgz")
        physical_to_index = numpy.linalg.inv(t1_image.get_affine())
        translation = tkregister_translation(asegfile, fsconfig)
        deformation = numpy.dot(physical_to_index, translation)

        # Load and warp the mesh
        # The mesh: a 2-uplet with vertex (x, y, z) coordinates and
        # mesh triangles
        mesh = freesurfer.read_geometry(fname)
        surf = TriSurface(vertices=apply_affine_on_mesh(mesh[0], deformation),
                          triangles=mesh[1])

        # Save the mesh in the native space
        outputfile = fname + ".native"
        surf.save(outputfile)
        csurffiles.append(outputfile)

        # Construct the surfaces binarized volume
        binarizedfile = os.path.join(outputfile + ".nii.gz")
        overlay = numpy.zeros(t1_image.shape, dtype=numpy.uint)
        indices = numpy.round(surf.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)

        # Clean input surface if specified
        if rm_orig:
            os.remove(fname)

    return csurffiles
コード例 #3
0
def surf_convert(fsdir,
                 t1files,
                 surffiles,
                 sidpos=-3,
                 rm_orig=False,
                 fsconfig=DEFAULT_FREESURFER_PATH):
    """ Export FreeSurfer surfaces to the native space.

    Note that all the returned vetices are given in the index coordinate
    system.

    The subject id in the t1 and surf files must appear in the 'sidpos'
    position. For the default value '-3', the T1 path might look like
    'xxx/subject_id/convert/t1.nii.gz'

    Parameters
    ----------
    fsdir: str (mandatory)
        The FreeSurfer working directory with all the subjects.
    t1files: str (mandatory)
        The t1 nifti files.
    surffiles:
        The surfaces to be converted.
    sidpos: int (optional, default -3)
        The subject identifier position in the surface and T1 files.
    rm_orig: bool (optional)
        If True remove the input surfaces.
    fsconfig: str (optional)
        The FreeSurfer configuration batch.

    Returns
    -------
    csurffiles:
        The converted surfaces in the native space indexed coordinates.
    """
    # Check input parameters
    for path in t1files + surffiles:
        if not os.path.isfile(path):
            raise ValueError("'{0}' is not a valid file.".format(path))
    if not os.path.isdir(fsdir):
        raise ValueError("'{0}' is not a valid directory.".format(fsdir))

    # Create a t1 subject map
    t1map = {}
    for fname in t1files:
        subject_id = fname.split(os.path.sep)[sidpos]
        if subject_id in t1map:
            raise ValueError("Can't map two t1 for subject "
                             "'{0}'.".format(subject_id))
        t1map[subject_id] = fname

    # Convert all the surfaces
    csurffiles = []
    for fname in surffiles:

        # Get the t1 reference image
        subject_id = fname.split(os.path.sep)[sidpos]
        t1file = t1map[subject_id]
        t1_image = nibabel.load(t1file)

        # Compute the conformed space to the native anatomical deformation
        asegfile = os.path.join(fsdir, subject_id, "mri", "aseg.mgz")
        physical_to_index = numpy.linalg.inv(t1_image.get_affine())
        translation = tkregister_translation(asegfile, fsconfig)
        deformation = numpy.dot(physical_to_index, translation)

        # Load and warp the mesh
        # The mesh: a 2-uplet with vertex (x, y, z) coordinates and
        # mesh triangles
        mesh = freesurfer.read_geometry(fname)
        surf = TriSurface(vertices=apply_affine_on_mesh(mesh[0], deformation),
                          triangles=mesh[1])

        # Save the mesh in the native space
        outputfile = fname + ".native"
        surf.save(outputfile)
        csurffiles.append(outputfile)

        # Construct the surfaces binarized volume
        binarizedfile = os.path.join(outputfile + ".nii.gz")
        overlay = numpy.zeros(t1_image.shape, dtype=numpy.uint)
        indices = numpy.round(surf.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)

        # Clean input surface if specified
        if rm_orig:
            os.remove(fname)

    return csurffiles
コード例 #4
0
ファイル: profiles.py プロジェクト: slefranc/pyfsl
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.
    """

    # 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 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
コード例 #5
0
def display_folds(folds_file,
                  labels,
                  weights,
                  white_file=None,
                  pits_file=None,
                  dist_indices=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.
    pits_file: str (optional, default None)
        if specified the PITS locations (need the white mesh).
    dist_indices: array (N, 2)
        a list of two white matter mesh vertex indices from which we compute
        a geodesic path.
    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
    folds = load_folds(folds_file, graph_file=None)

    # Create an actor for each fold
    ren = pvtk.ren()
    ren.SetBackground(1, 1, 1)
    for labelindex, surf in folds.items():
        if labelindex in labels:
            label = labels[labelindex]
            if label in weights:
                weight = weights[label] * 256.
            else:
                weight = 0
        else:
            label = "NC"
            weight = 0
        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))
        vertices = image.darrays[0].data
        triangles = image.darrays[1].data
        wm_surf = TriSurface(vertices, triangles, labels=None)
        actor = pvtk.surface(wm_surf.vertices,
                             wm_surf.triangles,
                             wm_surf.labels,
                             opacity=0.7,
                             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)

    # Add the PITS if specified
    if pits_file is not None and white_file is not None:
        image = gio.read(pits_file)
        nb_of_surfs = len(image.darrays)
        if nb_of_surfs != 1:
            raise ValueError("'{0}' does not a contain a valid pits "
                             "texture.".format(pits_file))
        pits_texture = image.darrays[0].data
        pits_locations = wm_surf.vertices[numpy.where(pits_texture == 1)]
        actor = pvtk.dots(pits_locations, color=(1, 0, 0), psize=20, opacity=1)
        actor.label = "pits"
        actor.RotateX(actor_ang[0])
        actor.RotateY(actor_ang[1])
        actor.RotateZ(actor_ang[2])
        pvtk.add(ren, actor)

    # Geodesic path
    if dist_indices is not None and white_file is not None:
        all_path = []
        for ind1, ind2 in dist_indices:
            all_path.append(
                wm_surf.geodesic_distance(vertices[ind1], vertices[ind2]))
        actor = pvtk.tubes(all_path, (0, 1, 0),
                           opacity=1,
                           linewidth=1,
                           tube_sides=8,
                           lod=True,
                           lod_points=10**4,
                           lod_points_size=5)
        actor.label = "geodesic"
        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", "pits", "geodesic"])
        pvtk.show(ren, title=name, 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)
コード例 #6
0
def display_pits_parcellation(white_file,
                              parcellation_file,
                              labels=None,
                              pits_file=None,
                              parcellation_as_annotation=False,
                              interactive=True,
                              snap=False,
                              animate=False,
                              outdir=None,
                              name="pits_parcellation",
                              actor_ang=(0., 0., 0.)):
    """ Display the pits parcellation.

    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 areal name).

    Parameters
    ----------
    white_file: str
        the white surface that will be displayed.
    parcellation_file: str
        the parcellation texture file.
    labels: dict, default None
        a mapping between an areal number and its name.
    pits_file: str, default None
        if specified the PITS locations.
    parcellation_as_annotation: bool, default False
        if set expect a FreeSurfer annotation file as a parcellation input.
    interactive: bool, default True
        if True display the renderer.
    snap: bool, default False
        if True create a snap of the scene: need a valid outdir.
    animate: bool, default False
        if True create a gif 360 degrees animation of the scene: need a valid
        outdir.
    outdir: str, default None
        an existing directory.
    name: str, default 'pits_parcellation'
        the basename of the generated files.
    actor_ang: 3-uplet, default (0, 0, 0)
        the actors x, y, z position (in degrees).
    """
    # Load the PITS if specified
    if pits_file is not None:
        image = gio.read(pits_file)
        nb_of_surfs = len(image.darrays)
        if nb_of_surfs != 1:
            raise ValueError("'{0}' does not a contain a valid pits "
                             "texture.".format(pits_file))
        pits_texture = image.darrays[0].data
    else:
        pits_texture = None

    # Create an actor for the white matter surface
    ren = pvtk.ren()
    ren.SetBackground(1, 1, 1)
    if white_file.endswith(".gii"):
        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))
        vertices = image.darrays[0].data
        triangles = image.darrays[1].data
    else:
        _surf = TriSurface.load(white_file)
        vertices = _surf.vertices
        triangles = _surf.triangles
    if parcellation_as_annotation:
        annotations = fio.read_annot(parcellation_file)
        texture, _, labels = annotations
    else:
        image_labels = gio.read(parcellation_file)
        texture = numpy.round(image_labels.darrays[0].data).astype(int)
    wm_surf = TriSurface(vertices, triangles, labels=texture.copy())

    # Four colors theorem to generate the cmap
    import networkx as nx
    import json
    # > define distinct colors
    colors_rgb = [(230, 25, 75), (60, 180, 75), (255, 225, 25), (0, 130, 200),
                  (245, 130, 48), (145, 30, 180),
                  (70, 240, 240), (240, 50, 230), (210, 245, 60),
                  (250, 190, 190), (0, 128, 128), (230, 190, 255),
                  (170, 110, 40), (255, 250, 200), (128, 0, 0),
                  (170, 255, 195), (128, 128, 0), (255, 215, 180), (0, 0, 128),
                  (128, 128, 128), (255, 255, 255)]
    # > create the graph nodes
    graph = nx.Graph()
    unique_labels = numpy.unique(texture)
    graph.add_nodes_from(unique_labels, color=None)
    # > get the cluster centroids & neighboor vertices
    clusters_map = {}
    for label in unique_labels:
        indices = numpy.where(wm_surf.labels == label)[0]
        cluster_triangles = wm_surf.triangles[list(
            numpy.where(numpy.isin(wm_surf.triangles, indices))[0])]
        cluster_indices = cluster_triangles[numpy.where(
            numpy.isin(cluster_triangles, indices, invert=True))]
        neighboors_indices = list(
            set(cluster_indices.astype(int)) - set(indices.astype(int)))
        clusters_map[label] = {
            "vertices": indices.tolist(),
            "neighboors": neighboors_indices
        }
    # > compute the graph edges
    edges = []
    nb_labels = len(unique_labels)
    for ind1 in range(nb_labels):
        for ind2 in range(ind1 + 1, nb_labels):
            label = unique_labels[ind1]
            other_label = unique_labels[ind2]
            if numpy.isin(clusters_map[other_label]["vertices"],
                          clusters_map[label]["neighboors"]).any():
                edges.append([label, other_label])
    graph.add_edges_from(edges)
    # > graph coloring
    colors = nx.algorithms.coloring.greedy_coloring.greedy_color(graph)
    ctab = []
    for label, color_id in colors.items():
        if label < 0:
            continue
        ctab.append(
            list(colors_rgb[color_id % len(colors_rgb)]) + [255., label])
    ctab.append([0., 0., 0., 255., unique_labels.max() + 1])
    ctab = numpy.asarray(ctab)

    # > create the actor
    wm_surf.labels = wm_surf.labels.astype(float)
    if pits_texture is not None:
        wm_surf.labels[numpy.where(pits_texture == 1)] = (unique_labels.max() +
                                                          1)
    wm_surf.labels[numpy.where(wm_surf.labels == -1)] = unique_labels.max() + 1
    actor = pvtk.surface(wm_surf.vertices,
                         wm_surf.triangles,
                         wm_surf.labels,
                         ctab=ctab,
                         opacity=1,
                         set_lut=True)
    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:
        pvtk.add(ren, actor)
        pvtk.show(ren, title=name)

    # 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)