Ejemplo n.º 1
0
def spherically_project_surface(insurf, outsurf):
    """ (string) -> None
    takes path to insurf, spherically projects it, outputs it to outsurf
    """
    surf = fs.read_geometry(insurf, read_metadata=True)
    projected = sphericalProject(surf[0], surf[1])
    fs.write_geometry(outsurf, projected[0], projected[1], volume_info=surf[2]) 
Ejemplo n.º 2
0
def save_surface_files(note, error, registrations, subject, no_surf_export,
                       no_reg_export, surface_format, surface_path, angle_tag,
                       eccen_tag, label_tag, radius_tag, registration_name):
    '''
    save_surface_files is the calculator that saves the registration data out as surface files,
    which are put back in the registration as the value 'surface_files'.
    '''
    if no_surf_export: return {'surface_files': ()}
    surface_format = surface_format.lower()
    # make an exporter for properties:
    if surface_format in ['curv', 'morph', 'auto', 'automatic']:

        def export(flnm, p):
            fsio.write_morph_data(flnm, p)
            return flnm
    elif surface_format in ['mgh', 'mgz']:

        def export(flnm, p):
            flnm = flnm + '.' + surface_format
            dt = np.int32 if np.issubdtype(p.dtype,
                                           np.dtype(int).type) else np.float32
            img = fsmgh.MGHImage(np.asarray([[p]], dtype=dt), np.eye(4))
            img.to_filename(flnm)
            return flnm
    elif surface_format in ['nifti', 'nii', 'niigz', 'nii.gz']:
        surface_format = 'nii' if surface_format == 'nii' else 'nii.gz'

        def export(flnm, p):
            flnm = flnm + '.' + surface_format
            dt = np.int32 if np.issubdtype(p.dtype,
                                           np.dtype(int).type) else np.float32
            img = nib.Nifti1Image(np.asarray([[p]], dtype=dt), np.eye(4))
            img.to_filename(flnm)
            return flnm
    else:
        error('Could not understand surface file-format %s' % surface_format)
    path = surface_path if surface_path else os.path.join(subject.path, 'surf')
    files = []
    note('Exporting files...')
    for h in six.iterkeys(registrations):
        hemi = subject.hemis[h]
        reg = registrations[h]
        note('Extracting %s predicted mesh...' % h.upper())
        pmesh = reg['predicted_mesh']
        for (pname, tag) in zip(
            ['polar_angle', 'eccentricity', 'visual_area', 'radius'],
            [angle_tag, eccen_tag, label_tag, radius_tag]):
            flnm = export(os.path.join(path, h + '.' + tag), pmesh.prop(pname))
            files.append(flnm)
        # last do the registration itself
        if registration_name and not no_reg_export:
            flnm = os.path.join(path,
                                h + '.' + registration_name + '.sphere.reg')
            fsio.write_geometry(flnm, pmesh.coordinates.T, pmesh.tess.faces.T)
    return {'surface_files': tuple(files)}
Ejemplo n.º 3
0
def export_fssurf(tria, outfile):
    """
    Save Freesurfer Surface Geometry file (wrap Nibabel)
    """
    # open file
    try:
        from nibabel.freesurfer.io import write_geometry
        write_geometry(outfile, tria.v, tria.t, volume_info=tria.fsinfo)
    except IOError:
        print("[File " + outfile + " not writable]")
        return
Ejemplo n.º 4
0
def save_freesurfer_geometry(filename, obj, volume_info=None, create_stamp=None):
    '''
    save_mgh(filename, obj) saves the given object to the given filename in the mgh format and
      returns the filename.

    All options that can be given to the to_mgh function can also be passed to this function; they
    are used to modify the object prior to exporting it.
    '''
    obj = geo.to_mesh(obj)
    fsio.write_geometry(filename, obj.coordinates.T, obj.tess.faces.T,
                        volume_info=volume_info, create_stamp=create_stamp)
    return filename
Ejemplo n.º 5
0
def resafe_surface(insurf, outsurf, pretess):
    """
    takes path to insurf and rewrites it to outsurf thereby fixing vertex locs flag error
    (scannerRAS instead of surfaceRAS after marching cube)
    :param str insurf: path and name of input surface
    :param str outsurf: path and name of output surface
    :param str pretess: path and name of file the input surface was created on (e.g. filled-pretess127.mgz)
    """
    surf = read_geometry(insurf, read_metadata=True)

    if not surf[2]['filename']:
        # Set information with file used for surface construction (volume info and name)
        surf[2]['filename'] = pretess
        surf[2]['volume'] = nibload(pretess).header.get_data_shape()

    fs.write_geometry(outsurf, surf[0], surf[1], volume_info=surf[2])
Ejemplo n.º 6
0
def makelayers(subjectid,layerdepths,layerprefix,fstruncate='pt'):
    '''
    def makelayers(subjectid,layerdepths,layerprefix,fstruncate)

    <subjectid> is like 'C0041'
    <layerdepths> is a vector of fractional distances (each having at
      most two decimal places).  e.g. linspace(.1,.9,6).
      NOTE: layerdepth=0 is pial and layerdepth=1 is white!
    <layerprefix> is a string that will be added to filenames (e.g. 'A').
      this causes files like 'lh.layerA1' to be made.
    <fstruncate> is the name of the truncation surface in fsaverage (default. 'pt',
      which refers to 'lh.pt' and 'rh.pt')

    Create layer surfaces.
    Subdivide layer and other surfaces to form dense surfaces.
    Calculate transfer functions to go from/to fsaverage standard surfaces
     and the single-subject dense surfaces.
    Truncate the dense surfaces based on the fsaverage <fstruncate> surface,
     and write out new surfaces.
    Calculate thickness, curvature, and sulc values for the single-subject dense surfaces.
    Calculate SAPV and AEL for each surface (dense trunc only).

    Turn on matlabpool before calling for a big speed-up.

    Example files that are created:
    lh.layerA1
    lh.layerA1DENSE
    lh.layerA1DENSETRUNCpt
    tfunDENSE.pkl
    lh.DENSETRUNCpt.pkl
    lh.curvatureDENSE.mgz
    lh.curvatureDENSETRUNCpt.mgz
    lh.sapv_sphere_DENSETRUNCpt.mgz

    history:
    - 2017/08/02 - added support for smoothwm
    - 2016/11/04 - added transfer functions for the fsaverage dense surfaces
    - 2016/04/29 - add saving of sulc, sapv, and ael start using unix_wrapper


    #######======== by RZ ===============================================
    history:
      20180717 RZ started to use pathos.multiprocessing to perform parallel computing
      20180714 RZ edited based on cvnmakelayers.m
    '''
    from RZutilpy.cvnpy import cvnpath,transfertodense
    from RZutilpy.math import mod2
    from RZutilpy.rzio import savepkl,loadpkl
    from math import floor
    import nibabel.freesurfer.io as fsio
    import numpy as np
    from pathos.multiprocessing import Pool

    # calc
    dir0 = (Path(cvnpath('anatomicals')) / subjectid).str
    fsdir =  (Path(cvnpath('freesurfer')) / subjectid).str
    fsdirAVG = (Path(cvnpath('freesurfer')) / 'fsaverage').str

    # define
    hemis = ['lh', 'rh']

    ########## create layer surfaces
    def expand(ii):
        p = mod2(ii,len(layerdepths)) # depth indx
        q = floor(ii / len(layerdepths)) # hemi index
        #use 1-depth so that depth 0 = pial and depth 1 = white
        # This way if you use linspace(.1,.9,6), A1 will be equivalent to canonical
        # layer I (molecular layer) and layer A6 will be equivalent to canonical
        # layer VI (innermost...)
        d = 1 - layerdepths[p - 1]
        unix_wrapper('mris_expand -thickness {wm} {depth:.2f} {outputsurf}'.format(\
          wm=(Path(fsdir)/'surf'/f'{hemis[q]}.white').str, \
          depth=d,\
          outputsurf=(Path(fsdir)/'surf'/f'{hemis[q]}.layer{layerprefix}{p}').str))
    if __name__ == '__main__':
        with Pool() as p:
            p.imap_unordered(expand, range(1, 2*len(layerdepths) + 1))


    ######## subdivide layer and other surfaces (creating dense surfaces)

    # calc a list of surfaces
    surfs = ['inflated', 'sphere', 'sphere.reg', 'white', 'pial', 'smoothwm']
    for p in range(1, len(layerdepths)+1):
        surfs.append(f'layer{layerprefix}{p}') # e.g. 'layerA1'


    # subdivide the surfaces
    def subdivide(ii):
        p = mod2(ii,len(surfs)) # surf index
        q = floor(ii/len(surfs)) # hemi index
        unix_wrapper('mris_mesh_subdivide --surf {inputsurf} --out {outputsurf} --method linear --iter 1'.format(\
          inputsurf=(Path(fsdir)/hemis[q]/surfs{p}).str,\
          outputsurf=(Path(fsdir)/hemis[q]/(surfs{p}+'DENSE')).str))
    if __name__ == '__main__':
        with Pool() as p:
            p.imap_unordered(subdivide, range(1, 2*len(surfs) + 1))

    ########## calculate some transfer functions for the dense surfaces [VERSION 1 (to standard fsaverage)]

    # calculate transfer functions
    [tfunFSSSlh,tfunFSSSrh,tfunSSFSlh,tfunSSFSrh] = \
      calctransferfunctions((Path(fsdirAVG)/'surf'/'lh.sphere.reg').str, \
                            (Path(fsdirAVG)/'surf'/'rh.sphere.reg').str, \
                            (Path(fsdir)/'surf'/'lh.sphere.regDENSE').str, \
                            (Path(fsdir)/'surf'/'rh.sphere.regDENSE').str)

    # save
    savepkl((Path(dir0) / 'tfunDENSE.pkl').str,\
      {'tfunFSSSlh':tfunFSSSlh,'tfunFSSSrh':tfunFSSSrh,\
      'tfunSSFSlh':tfunSSFSlh,'tfunSSFSrh':tfunSSFSrh})

    ######## calculate some transfer functions for the dense surfaces [VERSION 2 (to dense fsaverage)]
    # calc
    [tfunFSSSlh,tfunFSSSrh,tfunSSFSlh,tfunSSFSrh] = \
      calctransferfunctions((Path(fsdirAVG)/'surf'/'lh.sphere.regDENSE').str, \
                            (Path(fsdirAVG)/'surf'/'rh.sphere.regDENSE').str, \
                            (Path(fsdir)/'surf'/'lh.sphere.regDENSE').str, \
                            (Path(fsdir)/'surf'/'rh.sphere.regDENSE').str)

    # save
    savepkl((Path(dir0)/'tfunDENSEDENSE.pkl').str,\
      {'tfunFSSSlh':tfunFSSSlh,'tfunFSSSrh':tfunFSSSrh,\
      'tfunSSFSlh':tfunSSFSlh,'tfunSSFSrh':tfunSSFSrh})

    ########## revive the first version!!
    transfunc = loadpkl((Path(dir0)/'tfunDENSE.pkl').str)

    ########## truncate the dense surfaces based on the lh.<fstruncate> and rh.<fstruncate> fsaverage surfaces

    # calc number of vertices
    fsnumlh = fsio.read_geometry((Path(fsdirAVG)/'surf/'/'lh.white').str)[0].shape[0]
    fsnumrh = fsio.read_geometry((Path(fsdirAVG)/'surf/'/'rh.white').str)[0].shape[0]

    # do it
    for hemi in hemis:

        # calculate a vector of vertex values indicating which is included [fsaverage]

        ##!!! check here, should figure out read_patch_asc##
        surf = read_patch_asc((Path(fsdirAVG) / 'surf'/ f'{hemi}.{fstruncate}.patch.3d.asc').str)
        ##!!! ##

        vals = np.zeros(fsnumlh + fsnumrh)

        ## !!! ##
        if hemi == 'lh':
          vals[surf.vertices+1] = 1
        elif hemi == 'rh':
          vals[fsnumlh+(surf.vertices+1)] = 1
        ## !!! ##

        # transfer these values to the dense individual-subject surface and do a find.
        # this tells us indices of vertices in the dense surface that are valid
        validix = np.where(transfunc['tfunFSSSlh'][vals]) if hemi == 'lh' \
        else np.where(transfunc['tfunFSSSrh'][vals])

        # write out reduced surfaces
        for sf in surfs:

            # read in the original dense surface
            [verticesA,facesA,volinfo] = fsio.read_geometry((Path(fsdir)/'surf'/f'{hemi}.{sf}DENSE').str, read_metadata=True)

            # logical indicating which faces survive
            okfaces = np.all(np.isin(facesA,validix), axis=1)  # FACES x 1

            # calculate the new faces
            temp = facesA[okfaces,:]

            ##!!!##
            facesA = calcposition(validix, temp.flatten()).reshape(*temp.shape).copy()
            ##!!!##

            # calculate the new vertices
            verticesA = verticesA[validix,:]

            # write out the truncated surface
            fsio.write_geometry((Path(fsdir) / 'surf'/ f'{hemi}.{sf}DENSETRUNC{fstruncate}').str, verticesA, facesA, volinfo)


      # save the DENSE->DENSETRUNC indices (truncsize x 1)
      savepkl((Path(fsdir)/'surf'/f'{hemi}.DENSETRUNC{fstruncate}.pkl').str, {'validix':validix})


      # save the orig->DENSE indices (densesize x 1)
      numverts_orig = fsio.read_geometry((Path(fsdir)/'surf'/f'{hemi}.inflated').str)[0].shape[0]

      # note the 0 inde problem here, here might not be correct
      validix = transfertodense(subjectid, np.arange(numverts_orig), hemi,'nearest','inflated')
      savepkl((fsdi/'surf'/f'{hemi}.DENSE.pkl').str,{'validix':validix})
Ejemplo n.º 7
0
def deform_surface(input_surf,
                   input_orig,
                   input_deform,
                   input_target,
                   hemi,
                   path_output,
                   input_mask=None,
                   interp_method="nearest",
                   smooth_iter=0,
                   flip_faces=False,
                   cleanup=True):
    """
    This function deforms a surface mesh in freesurfer convention using a coordinate map containing
    voxel coordinates. The computation takes quite a while because in the case of removed vertices,
    i.e. if a mask is given as input, the remaining faces are reindexed.
    Inputs:
        *input_surf: surface mesh to be transformed.
        *input_orig: freesurfer orig.mgz.
        *input_deform: deformation (coordinate mapping).
        *input_target: target volume.
        *hemi: hemisphere.
        *path_output: path where to save output.
        *input_mask: mask volume.
        *interp_method: interpolation method (nearest or trilinear).
        *smooth_iter: number of smoothing iterations applied to final image (if set > 0).
        *flip_faces: reverse normal direction of mesh.
        *cleanup: remove intermediate files.
        
    created by Daniel Haenelt
    Date created: 06-02-2019          
    Last modified: 20-06-2020
    """
    import os
    import numpy as np
    import nibabel as nb
    import shutil as sh
    from nibabel.freesurfer.io import write_geometry, read_geometry
    from nibabel.affines import apply_affine
    from nipype.interfaces.freesurfer import SampleToSurface
    from nipype.interfaces.freesurfer import SmoothTessellation
    from lib.io.get_filename import get_filename
    from lib.io.mgh2nii import mgh2nii
    from lib.surface.vox2ras import vox2ras

    # set freesurfer path environment
    os.environ["SUBJECTS_DIR"] = path_output

    # freesurfer subject
    tmp = np.random.randint(0, 10, 5)
    tmp_string = ''.join(str(i) for i in tmp)
    sub = "tmp_" + tmp_string

    # make output folder
    if not os.path.exists(path_output):
        os.makedirs(path_output)

    # mimic freesurfer folder structure (with some additional folder for intermediate files)
    path_sub = os.path.join(path_output, sub)
    path_mri = os.path.join(path_sub, "mri")
    path_surf = os.path.join(path_sub, "surf")

    os.makedirs(path_sub)
    os.makedirs(path_mri)
    os.makedirs(path_surf)

    # get file extension of orig
    _, name_orig, ext_orig = get_filename(input_orig)

    # name of surface file
    name_surf = os.path.basename(input_surf)

    # copy orig, cmap and input surface to mimicked freesurfer folders
    sh.copyfile(input_surf, os.path.join(path_surf, hemi + ".source"))
    if ext_orig != ".mgz":
        mgh2nii(input_orig, path_mri, "mgz")
        os.rename(os.path.join(path_mri, name_orig + ".mgz"),
                  os.path.join(path_mri, "orig.mgz"))
    else:
        sh.copyfile(input_orig, os.path.join(path_mri, "orig.mgz"))

    # read surface geometry
    vtx, fac = read_geometry(input_surf)

    # get affine vox2ras-tkr transformation to target volume
    vox2ras_tkr, _ = vox2ras(input_target)

    # divide coordinate mapping into its x, y and z components
    cmap_img = nb.load(input_deform)
    cmap_img.header["dim"][0] = 3
    cmap_img.header["dim"][4] = 1

    # apply vox2ras transformation to coordinate mappings
    cmap_array = cmap_img.get_fdata()
    cmap_array = apply_affine(vox2ras_tkr, cmap_array)

    components = ["x", "y", "z"]
    vtx_new = np.zeros([len(vtx), 3])
    for i in range(len(components)):
        temp_array = cmap_array[:, :, :, i]
        temp_img = nb.Nifti1Image(temp_array, cmap_img.affine, cmap_img.header)
        nb.save(temp_img, os.path.join(path_mri,
                                       components[i] + "_deform.nii"))

        # mri_vol2surf
        sampler = SampleToSurface()
        sampler.inputs.subject_id = sub
        sampler.inputs.reg_header = True
        sampler.inputs.hemi = hemi
        sampler.inputs.source_file = os.path.join(
            path_mri, components[i] + "_deform.nii")
        sampler.inputs.surface = "source"
        sampler.inputs.sampling_method = "point"
        sampler.inputs.sampling_range = 0
        sampler.inputs.sampling_units = "mm"
        sampler.inputs.interp_method = interp_method
        sampler.inputs.out_type = "mgh"
        sampler.inputs.out_file = os.path.join(
            path_surf, hemi + "." + components[i] + "_sampled.mgh")
        sampler.run()

        data_img = nb.load(
            os.path.join(path_surf,
                         hemi + "." + components[i] + "_sampled.mgh"))
        vtx_new[:, i] = np.squeeze(data_img.get_fdata())

    if input_mask:

        # mri_vol2surf (background)
        sampler = SampleToSurface()
        sampler.inputs.subject_id = sub
        sampler.inputs.reg_header = True
        sampler.inputs.hemi = hemi
        sampler.inputs.source_file = input_mask
        sampler.inputs.surface = "source"
        sampler.inputs.sampling_method = "point"
        sampler.inputs.sampling_range = 0
        sampler.inputs.sampling_units = "mm"
        sampler.inputs.interp_method = "nearest"
        sampler.inputs.out_type = "mgh"
        sampler.inputs.out_file = os.path.join(path_surf,
                                               hemi + ".background.mgh")
        sampler.run()

        # get new indices
        background_list = nb.load(
            os.path.join(path_surf, hemi + ".background.mgh")).get_fdata()
        background_list = np.squeeze(background_list).astype(int)

        # only keep vertex indices within the slab
        ind_keep = np.arange(0, len(vtx[:, 0]))
        ind_keep[background_list == 0] = -1
        ind_keep = ind_keep[ind_keep != -1]

        # get new vertices
        vtx_new = vtx_new[ind_keep, :]

        # get new faces
        fac_keep = np.zeros(len(fac[:, 0]))
        fac_keep += np.in1d(fac[:, 0], ind_keep)
        fac_keep += np.in1d(fac[:, 1], ind_keep)
        fac_keep += np.in1d(fac[:, 2], ind_keep)
        fac_temp = fac[fac_keep == 3, :]
        fac_new = fac[fac_keep == 3, :]

        # sort new faces
        c_step = 0
        n_step = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
        for i in range(len(ind_keep)):
            temp = np.where(ind_keep[i] == fac_temp)
            fac_new[temp] = i

            # print status
            counter = np.floor(i / len(ind_keep) * 100).astype(int)
            if counter == n_step[c_step]:
                print("sort faces: " + str(counter) + " %")
                c_step += 1

        # remove singularities (vertices without faces)
        fac_counter = 0
        fac_old = fac_new.copy()
        n_singularity = np.zeros(len(vtx_new))
        c_step = 0
        for i in range(len(vtx_new)):
            row, col = np.where(fac_old == i)

            n_singularity[i] = len(row)
            if not n_singularity[i]:
                fac_temp = fac_new.copy()
                fac_temp[fac_temp >= fac_counter] = -1
                fac_temp[fac_temp != -1] = 0
                fac_new += fac_temp
                fac_counter -= 1

            # update face counter
            fac_counter += 1

            # print status
            counter = np.floor(i / len(vtx_new) * 100).astype(int)
            if counter == n_step[c_step]:
                print("clean vertices: " + str(counter) + " %")
                c_step += 1

        # vertices and indices without singularities
        vtx_new = vtx_new[n_singularity != 0]
        ind_keep = ind_keep[n_singularity != 0]

        # save index mapping between original and transformed surface
        np.savetxt(os.path.join(path_output, name_surf + "_ind.txt"),
                   ind_keep,
                   fmt='%d')
    else:
        fac_new = fac

    # flip faces
    if flip_faces:
        fac_new = np.flip(fac_new, axis=1)

    # write new surface
    write_geometry(os.path.join(path_output, name_surf + "_def"), vtx_new,
                   fac_new)

    # smooth surface
    if smooth_iter:
        smooth = SmoothTessellation()
        smooth.inputs.in_file = os.path.join(path_output, name_surf + "_def")
        smooth.inputs.out_file = os.path.join(path_output,
                                              name_surf + "_def_smooth")
        smooth.inputs.smoothing_iterations = smooth_iter
        smooth.inputs.disable_estimates = True
        smooth.run()

    # delete intermediate files
    if cleanup:
        sh.rmtree(path_sub, ignore_errors=True)
Ejemplo n.º 8
0
def run_anchor(vtx, fac, adjm, anchor, n_neighbor=20, smooth_iter=0):
    """ Run anchor

    This function loads a list of control points and shifts a mesh with its 
    local neighborhood to match these control points. Control points are assumed 
    to be in ras coordinates. Optionally, the output mesh can be smoothed.       

    Parameters
    ----------
    vtx : ndarray
        Array of vertex points.
    fac : ndarray
        Array of corresponding faces.
    adjm : obj
        Adjacency matrix.
    anchor : list
        List of control points.
    n_neighbor : int, optional
        Neighborhood size. The default is 20.
    smooth_iter : int, optional
        Number of smoothing iterations of final mesh. The default is 0.

    Returns
    -------
    vtx : ndarray
        Shifted array of vertex points.
    ind_control : ndarray
        Indices of closest vertices to control points.

    Notes
    -------
    created by Daniel Haenelt
    Date created: 12-05-2020 
    Last modified: 05-10-2020  

    """

    print("start mesh initialization (anchoring)")

    # check vein array
    if anchor is None:
        sys.exit("error: no control points found for anchoring!")

    # number of anchor points
    n_anchor = len(anchor)

    # loop through control points
    ind_control = np.zeros(n_anchor).astype(int)
    for i in range(n_anchor):

        # print current status
        print("apply control point: " + str(i + 1) + "/" + str(n_anchor))

        # get nearest vertex
        vtx_dist = norm(vtx - anchor[i, :], axis=1)
        ind_min = np.where(vtx_dist == np.min(vtx_dist))[0][0]
        ind_control[i] = ind_min

        # get neighborhood
        nn_ind = nn_2d(ind_min, adjm, n_neighbor)

        # get shift
        vtx_shift = vtx[ind_min, :] - anchor[i, :]

        # update mesh
        vtx = update_mesh(vtx, vtx_shift, ind_min, nn_ind, 1)

    # smooth output
    if smooth_iter:
        tmp = np.random.randint(0, 10, 5)
        tmp_string = ''.join(str(i) for i in tmp)
        surf_temp = os.path.join(os.getcwd(), "surf_" + tmp_string)
        write_geometry(surf_temp, vtx, fac)
        smooth_surface(surf_temp, surf_temp, smooth_iter)
        vtx, _ = read_geometry(surf_temp)
        os.remove(surf_temp)

    return vtx, ind_control
Ejemplo n.º 9
0
os.chdir(join(pathLIB, "registration"))
os.system("matlab" + \
          " -nodisplay -nodesktop -r " + \
          "\"run_rBBR(\'{0}\', \'{1}\', \'{2}\'); exit;\"". \
          format(input_cfg, pathSPM, pathMOURIK))
os.chdir(cwd)

# vox2ras
data = loadmat(out_surf_mat_vox)

# apply ras2vox transformation to output
for i in range(2):
    data["wSurface"][0][i] = data["wSurface"][0][i][:, 0:3]
    data["pSurface"][0][i] = data["pSurface"][0][i][:, 0:3]

    data["wSurface"][0][i] = apply_affine(vox2ras_tkr, data["wSurface"][0][i])
    data["pSurface"][0][i] = apply_affine(vox2ras_tkr, data["pSurface"][0][i])

# save matfile
savemat(out_surf_mat_ras, data)

# save surfaces in freesurfer format
write_geometry(join(subjects_dir, "lh.white"), data["wSurface"][0][0],
               data["faceData"][0][0])
write_geometry(join(subjects_dir, "rh.white"), data["wSurface"][0][1],
               data["faceData"][0][1])
write_geometry(join(subjects_dir, "lh.pial"), data["pSurface"][0][0],
               data["faceData"][0][0])
write_geometry(join(subjects_dir, "rh.pial"), data["pSurface"][0][1],
               data["faceData"][0][1])
def calculate_equivolumetric_surfaces(file_white, file_pial, n_surfs, factor,
                                      niter, hemi, path_output):
    """
    The script calculates intracortical surfaces based on equi-volumetric layering. It is an 
    adaption of Konrad Wagstyl's function in surface_tools. Here, the io_mesh is not used anymore 
    and the call to a freesurfer function is omitted. Instead, vertex-wise area is calculated in a 
    separate function and we use the nibabel to read the surface geometry. First, vertex-wise area 
    is calculated from both input geometries. Smoothing to the areas is optional and done if factor 
    is set to a non-zero value. Then, based on vertex-wise area, equi-volumetric surfaces are 
    computed.
    Inputs:
        *file_white: input of GM/WM surface.
        *file_pial: input of GM/CSF surface.
        *n_surfs: number of output surfaces (returns input surfaces as 0 and 1).
        *factor: amount of smoothing.
        *niter: number of smoothing iterations.
        *hemi: declare hemisphere for output file.
        *path_output: path where output is saved.
    
    created by Daniel Haenelt
    Date created: 01-11-2018             
    Last modified: 10-06-2020
    """
    import os
    import numpy as np
    from nibabel.freesurfer.io import read_geometry, write_geometry
    from cortex.polyutils import Surface
    from lib.segmentation.calculate_area import calculate_area

    def beta(alpha, aw, ap):
        """Compute euclidean distance fraction, beta, that will yield the desired
        volume fraction, alpha, given vertex areas in the white matter surface, 
        aw, and on the pial surface, ap.
    
        A surface with `alpha` fraction of the cortical volume below it and 
        `1 - alpha` fraction above it can then be constructed from pial, px, and 
        white matter, pw, surface coordinates as `beta * px + (1 - beta) * pw`.
        """
        if alpha == 0:
            return np.zeros_like(aw)
        elif alpha == 1:
            return np.ones_like(aw)
        else:
            return 1 - (1 / (ap - aw) *
                        (-aw + np.sqrt((1 - alpha) * ap**2 + alpha * aw**2)))

    # make output folder
    if not os.path.exists(path_output):
        os.makedirs(path_output)

    # load geometry and area data
    wm_vtx, wm_fac = read_geometry(file_white)
    pial_vtx, pial_fac = read_geometry(file_pial)
    wm_vertexareas = calculate_area(file_white)
    pial_vertexareas = calculate_area(file_pial)

    # smoothing area files (optional)
    if factor != 0:
        wm_vertexareas = Surface(wm_vtx, wm_fac).smooth(wm_vertexareas,
                                                        factor=factor,
                                                        iterations=niter)
        pial_vertexareas = Surface(pial_vtx, pial_fac).smooth(pial_vertexareas,
                                                              factor=factor,
                                                              iterations=niter)

    # number of equally space intracortical surfaces
    vectors = wm_vtx - pial_vtx
    tmp_vtx = pial_vtx.copy()
    tmp_fac = pial_fac.copy()
    mask = vectors.sum(
        axis=1) != 0  # create mask where vertex coordinates match

    for depth in range(n_surfs):
        print("creating surface " + str(depth + 1))
        betas = beta(
            float(depth) / (n_surfs - 1), wm_vertexareas[mask],
            pial_vertexareas[mask])
        betas = np.nan_to_num(betas)
        tmp_vtx[mask] = pial_vtx[mask] + vectors[mask] * np.array([betas]).T
        write_geometry(
            os.path.join(path_output, hemi + "." + "layer" + str(depth)),
            tmp_vtx, tmp_fac)
Ejemplo n.º 11
0
def plot_normal(vtx, fac, adjm, file_out, step_size=100, shape="line"):
    """ Plot normal

    This function generates lines to visualize outward directed surface normals 
    of an input surface mesh.

    Parameters
    ----------
    vtx : ndarray
        Array of vertex points.
    fac : ndarray
        Corresponding face array.
    adjm : obj
        Adjacency matrix.
    file_out : str
        Filename of output surface mesh.
    step_size : int, optional
        Subset of vertices. The default is 100.
    shape : str, optional
        line, triangle, prism. The default is "line".

    Returns
    -------
    None.

    Notes
    -------
    created by Daniel Haenelt
    Date created: 13-12-2019
    Last modified: 05-10-2020
    
    """

    # get surface normals
    normal = get_normal(vtx, fac)

    # array containing a list of considered vertices
    t = np.arange(0, len(vtx), step_size)

    # initialise faces for specific shape
    if shape == "prism":
        fac_new = [[0, 1, 2], [3, 4, 5], [0, 1, 4], [0, 3, 4], [1, 2, 5],
                   [1, 4, 5], [0, 2, 5], [0, 3, 5]]
        fac_iter = 6
    elif shape == "triangle":
        fac_new = [[0, 1, 2]]
        fac_iter = 3
    elif shape == "line":
        fac_new = [[0, 1, 0]]
        fac_iter = 2

    vtx_res = []
    fac_res = []
    for i in range(len(t)):

        # get index from nearest neighbour of a given vertex
        nn = nn_2d(t[i], adjm, 0)
        nn = nn[:2]

        # get all vertex points for specific shape
        if shape == "prism":
            A = list(vtx[t[i]])
            B = list(vtx[nn[0]])
            C = list(vtx[nn[1]])
            D = list(vtx[t[i]] - normal[t[i]])
            E = list(vtx[nn[0]] - normal[nn[0]])
            F = list(vtx[nn[1]] - normal[nn[1]])
            vtx_new = [A, B, C, D, E, F]
        elif shape == "triangle":
            A = list(vtx[t[i]])
            B = list(vtx[nn[0]])
            C = list(vtx[t[i]] - normal[t[i]])
            vtx_new = [A, B, C]
        elif shape == "line":
            A = list(vtx[t[i]])
            B = list(vtx[t[i]] - normal[t[i]])
            vtx_new = [A, B]

        # update faces
        if i > 0:
            for j in range(len(fac_new)):
                fac_new[j] = [x + fac_iter for x in fac_new[j]]

        # update resulting vertex and face list
        vtx_res.extend(vtx_new)
        fac_res.extend(fac_new)

    # vertices and faces as array
    vtx_res = np.array(vtx_res)
    fac_res = np.array(fac_res)

    # write output geometry
    write_geometry(file_out, vtx_res, fac_res)
Ejemplo n.º 12
0
def register_retinotopy_command(args):
    '''
    register_retinotopy_command(args) can be given a list of arguments, such as sys.argv[1:]; these
    arguments may include any options and must include at least one subject id. All subjects whose
    ids are given are registered to a retinotopy model, and the resulting registration, as well as
    the predictions made by the model in the registration, are exported.
    '''
    # Parse the arguments
    (args, opts) = _retinotopy_parser(args)
    # First, help?
    if opts['help']:
        print register_retinotopy_help
        return 1
    # and if we are verbose, lets setup a note function
    verbose = opts['verbose']
    def note(s):
        if verbose: print s
        return verbose
    # Add the subjects directory, if there is one
    if 'subjects_dir' in opts and opts['subjects_dir'] is not None:
        add_subject_path(opts['subjects_dir'])
    # Parse the simple numbers
    for o in ['weight_cutoff', 'edge_strength', 'angle_strength', 'func_strength',
              'max_step_size', 'max_out_eccen']:
        opts[o] = float(opts[o])
    opts['max_steps'] = int(opts['max_steps'])
    # These are for now not supported: #TODO
    if opts['angle_math'] or opts['angle_radians'] or opts['eccen_radians']:
        print 'Mathematical angles and angles not in degrees are not yet supported.'
        return 1
    # The remainder of the args can wait for now; walk through the subjects:
    tag_key = {'eccen': 'eccentricity', 'angle': 'polar_angle', 'label': 'V123_label'}
    for subnm in args:
        sub = freesurfer_subject(subnm)
        note('Processing subject: %s' % sub.id)
        # we need to register this subject...
        res = {}
        ow = not opts['no_overwrite']
        for h in ['LH','RH']:
            note('   Processing hemisphere: %s' % h)
            hemi = sub.__getattr__(h)
            # See if we are loading custom values...
            (ang,ecc,wgt) = (None,None,None)
            suffix = '_' + h.lower() + '_file'
            if opts['angle'  + suffix] is not None: ang = _guess_surf_file(opts['angle'  + suffix])
            if opts['eccen'  + suffix] is not None: ecc = _guess_surf_file(opts['eccen'  + suffix])
            if opts['weight' + suffix] is not None: wgt = _guess_surf_file(opts['weight' + suffix])
            # Do the registration
            note('    - Running Registration...')
            res[h] = register_retinotopy(hemi, V123_model(),
                                         polar_angle=ang, eccentricity=ecc, weight=wgt,
                                         weight_cutoff=opts['weight_cutoff'],
                                         partial_voluming_correction=opts['part_vol_correct'],
                                         edge_scale=opts['edge_strength'],
                                         angle_scale=opts['angle_strength'],
                                         functional_scale=opts['func_strength'],
                                         prior=opts['prior'],
                                         max_predicted_eccen=opts['max_out_eccen'],
                                         max_steps=opts['max_steps'],
                                         max_step_size=opts['max_step_size'])
            # Perform the hemi-specific outputs now:
            if not opts['no_reg_export']:
                regnm = '.'.join([h.lower(), opts['registration_name'], 'sphere', 'reg'])
                flnm = (os.path.join(sub.directory, 'surf', regnm) if h == 'LH' else
                        os.path.join(sub.directory, 'xhemi', 'surf', regnm))
                if ow or not os.path.exist(flnm):
                    note('    - Exporting registration file: %s' % flnm)
                    fsio.write_geometry(flnm, res[h].coordinates.T, res[h].faces.T,
                                        'Created by neuropythy (github.com/noahbenson/neuropythy)')
                else:
                    note('    - Skipping registration file: %s (file exists)' % flnm)
            if not opts['no_surf_export']:
                for dim in ['angle', 'eccen', 'label']:
                    flnm = os.path.join(sub.directory, 'surf',
                                        '.'.join([h.lower(), opts[dim + '_tag'], 'mgz']))
                    if ow or not os.path.exist(flnm):
                        note('    - Exporting prediction file: %s' % flnm)
                        img = fsmgh.MGHImage(
                            np.asarray([[res[h].prop(tag_key[dim])]],
                                       dtype=(np.int32 if dim == 'label' else np.float32)),
                            np.eye(4))
                        img.to_filename(flnm)
                    else:
                        note('    - Skipping prediction file: %s (file exists)' % flnm)
        # Do the volume exports here
        if not opts['no_vol_export']:
            note('   Processing volume data...')
            note('    - Calculating cortex-to-ribbon mapping...')
            surf2rib = cortex_to_ribbon_map(sub, hemi=None)
            for dim in ['angle', 'eccen', 'label']:
                flnm = os.path.join(sub.directory, 'mri', opts[dim + '_tag'] + '.mgz')
                if ow or not os.path.exist(flnm):
                    note('    - Generating volume file: %s' % flnm)
                    vol = cortex_to_ribbon(sub,
                                           (res['LH'].prop(tag_key[dim]),
                                            res['RH'].prop(tag_key[dim])),
                                           map=surf2rib,
                                           dtype=(np.int32 if dim == 'label' else np.float32))
                    note('    - Exporting volume file: %s' % flnm)
                    vol.to_filename(flnm)
                else:
                    note('    - Skipping volume file: %s (file exists)' % flnm)
        # That is it for this subject!
        note('   Subject %s finished!' % sub.id)
    # And if we made it here, all was successful.
    return 0    
Ejemplo n.º 13
0
 def write(self, surface, surface_path):
     write_geometry(filepath=surface_path, coords=surface.vertices, faces=surface.triangles,
                    volume_info=surface.get_main_metadata())
Ejemplo n.º 14
0
def remove_vertex_outliers(input_surf, input_ind, n=5, overwrite=True):
    """
    This function removes outlier vertices from a deformed surface mesh. Due
    to interpolation of the deformation fields, edge effects can move some 
    vertices to the edge of the image volume. These are removed by comparing 
    each vertex to the geometric center of the whole mesh and setting a global
    threshold. The threshold is defined as multiple (n) of the standard 
    deviation of the distance distribution. This is a very crude method to delete
    outlier vertices.
    Inputs:
        *input_surf: path to the input surface mesh.
        *input_ind: path to the corresponding index list (with .txt extension)
        *n: distance threshold parameter.
        *overwrite: overwrite input surface.
        
    created by Daniel Haenelt
    Date created: 08-12-2019 
    Last modified: 09-12-2019
    """
    import os
    import numpy as np
    from nibabel.freesurfer.io import read_geometry, write_geometry

    # load geometry
    vtx, fac = read_geometry(input_surf)
    
    # load index file
    ind = np.loadtxt(input_ind)

    # get geometry center
    x_mean = np.sum(vtx[:,0]) / len(vtx[:,0])
    y_mean = np.sum(vtx[:,1]) / len(vtx[:,1])
    z_mean = np.sum(vtx[:,2]) / len(vtx[:,2])

    # euclidean distance to geometric center
    vtx_dist = np.zeros_like(vtx)
    vtx_dist[:,0] = ( vtx[:,0] - x_mean ) ** 2
    vtx_dist[:,1] = ( vtx[:,1] - y_mean ) ** 2
    vtx_dist[:,2] = ( vtx[:,2] - z_mean ) ** 2
    vtx_dist = np.sqrt( np.sum(vtx_dist, 1) )

    # mean and std distance
    vtx_dist_mean = np.mean(vtx_dist)
    vtx_dist_std = np.std(vtx_dist)

    # distance threshold
    vtx_dist_threshold = vtx_dist_mean + n * vtx_dist_std

    # sort faces
    fac_old = fac.copy()
    fac_outlier = np.zeros_like(fac)
    n_outlier = np.zeros(len(vtx))
    c_step = 0
    n_step = [10,20,30,40,50,60,70,80,90,100]
    for i in range(len(vtx)):    
        if vtx_dist[i] > vtx_dist_threshold:
            row, col = np.where(fac_old == i)
            fac_outlier[row, col] = 1 # remember which faces to remove
            n_outlier[i] = 1 # remember which vertices to remove
            fac_temp = fac.copy() # update face numbering
            fac_temp[fac_old >= i] = -1
            fac_temp[fac_temp != -1] = 0
            fac += fac_temp

        # print status
        counter = np.floor(i / len(vtx) * 100).astype(int)
        if counter == n_step[c_step]:
            print("remove outliers: "+str(counter)+" %")
            c_step += 1

    # remove outlier faces
    fac_outlier = np.sum(fac_outlier,1)
    fac = fac[fac_outlier == 0]

    # remove outliers in vertex and ind
    vtx = vtx[n_outlier == 0]
    ind = ind[n_outlier == 0]

    # write output
    if overwrite:
        write_geometry(input_surf,vtx,fac)
        np.savetxt(input_ind, ind, fmt='%d')
    else:
        path_output = os.path.dirname(input_surf)
        name_output = os.path.basename(input_surf)
        write_geometry(os.path.join(path_output,name_output+"_out"),vtx,fac)
        
        path_output = os.path.dirname(input_ind)
        name_output = os.path.splitext(os.path.basename(input_ind))[0]
        np.savetxt(os.path.join(path_output,name_output+"_out.txt"), ind, fmt='%d')
Ejemplo n.º 15
0
 def write_fs(self, surface, surface_path):
     write_geometry(filepath=surface_path, coords=surface.vertices, faces=surface.triangles, volume_info=surface.image_metadata)
Ejemplo n.º 16
0
def match_vertex_number(input_white_surf, input_pial_surf, input_white_ind, input_pial_ind, 
                        path_output):
    """
    This function takes a white and a pial surface as input and matches the vertex numbers of both
    surfaces. Removed vertices (not found in the corresponding other surface) are removed and faces
    are updated. The index files are updated as well.
    Inputs:
        *input_white_surf: filename of white surface.
        *input_pial_surf: filename of pial surface.
        *input_white_ind: corresponding index file of white surface.
        *input_pial_ind: corresponding index file of pial surface.
        *path_output: path where output is written.
        
    created by Daniel Haenelt
    Date created: 10-12-2019
    Last modified: 09-01-2020
    """
    import os
    import numpy as np
    from nibabel.freesurfer.io import read_geometry, write_geometry

    # make output folder
    if not os.path.exists(path_output):
        os.makedirs(path_output)

    # load geometry
    vtx_white, fac_white = read_geometry(input_white_surf)
    vtx_pial, _ = read_geometry(input_pial_surf)

    # load index file
    white_ind = np.loadtxt(input_white_ind).astype(int)
    pial_ind = np.loadtxt(input_pial_ind).astype(int)

    # vertex indices in orig space which are in neither of both deformed surfaces
    ind_remove = list(set(pial_ind) - set(white_ind))
    ind_remove.extend(set(white_ind) - set(pial_ind))
    ind_remove = np.sort(ind_remove) 

    # sort vertices of white surface
    i = 0
    c_white = np.zeros_like(white_ind)
    while i < np.size(white_ind):
        if np.any(ind_remove == white_ind[i]):
            c_white[i] = 1
    
        i += 1    

    # sort vertices of pial surface
    i = 0
    c_pial = np.zeros_like(pial_ind)
    while i < np.size(pial_ind):
        if np.any(ind_remove == pial_ind[i]):
            c_pial[i] = 1

        i += 1  

    # sort faces
    fac_old = fac_white.copy()
    fac_new = fac_white.copy()
    fac_outlier = np.zeros_like(fac_white)
    c_step = 0
    n_step = [10,20,30,40,50,60,70,80,90,100]
    for i in range(len(ind_remove)):
        check_remove = np.where(ind_remove[i] == white_ind)[0]
        if len(check_remove):
            row, col = np.where(fac_old == check_remove)
            fac_outlier[row,col] = 1 # remember which faces to remove
            fac_temp = fac_new.copy() # update face numbering
            fac_temp[fac_old > check_remove] = -1
            fac_temp[fac_temp != -1] = 0
            fac_new += fac_temp
    
        i += 1
        
        # print status
        counter = np.floor(i / len(ind_remove) * 100).astype(int)
        if counter == n_step[c_step]:
            print("sort faces: "+str(counter)+" %")
            c_step += 1

    # remove outlier faces
    fac_outlier = np.sum(fac_outlier,1)
    fac_new = fac_new[fac_outlier == 0]

    # remove outliers in vertices
    vtx_white = vtx_white[c_white == 0]
    vtx_pial = vtx_pial[c_pial == 0]

    # remove outliers in ind
    white_ind = white_ind[c_white == 0]
    pial_ind = pial_ind[c_pial == 0]

    # write output
    name_output = os.path.basename(input_white_surf)
    write_geometry(os.path.join(path_output, name_output+"_match"),vtx_white,fac_new)

    name_output = os.path.basename(input_pial_surf)
    write_geometry(os.path.join(path_output, name_output+"_match"),vtx_pial,fac_new)
    
    name_output = os.path.basename(input_white_ind)
    np.savetxt(os.path.join(path_output, name_output+"_match.txt"), white_ind, fmt='%d')

    name_output = os.path.basename(input_pial_ind)
    np.savetxt(os.path.join(path_output, name_output+"_match.txt"), pial_ind, fmt='%d')
Ejemplo n.º 17
0
    def create_subcorts(self):

        # Important filepaths
        fsDir = self.subdir
        dataDir = self.scDir + os.sep + 'data'
        tmpDir = os.path.join(dataDir, 'tmp')
        os.makedirs(tmpDir)
        # Dictionary of labels and corresponding values
        subcort_dict = {
            'L_Thalamus': {
                'id': '10',
                'color': '#b5ffbe'
            },
            'L_Caudate': {
                'id': '11',
                'color': '#d7cfff'
            },
            'L_Putamen': {
                'id': '12',
                'color': '#fa05b9'
            },
            'L_Pallidum': {
                'id': '13',
                'color': '#a2a3a2'
            },
            'L_Hippocampus': {
                'id': '17',
                'color': '#f7ffcc'
            },
            'L_Amygdala': {
                'id': '18',
                'color': '#ffbfc1'
            },
            'R_Thalamus': {
                'id': '49',
                'color': '#b5ffbe'
            },
            'R_Caudate': {
                'id': '50',
                'color': '#d7cfff'
            },
            'R_Putamen': {
                'id': '51',
                'color': '#fa05b9'
            },
            'R_Pallidum': {
                'id': '52',
                'color': '#a2a3a2'
            },
            'R_Hippocampus': {
                'id': '53',
                'color': '#f7ffcc'
            },
            'R_Amygdala': {
                'id': '54',
                'color': '#ffbfc1'
            }
        }

        # Create the meshes using freesurfer commands
        for subc in subcort_dict.keys():
            # Set names of files that will be used in this loop
            pretessF = tmpDir + os.sep + subc + '_pretess.mgz'
            tessF = tmpDir + os.sep + subc + '_tess.lab'
            smoothF = tmpDir + os.sep + subc + '_tess.smooth'
            subcortF = dataDir + os.sep + subc.lower().replace('_', 'h.')

            # Pretess
            pretess_cmd_tmp = [
                'mri_pretess',
                os.path.join(fsDir, 'mri', 'aseg.mgz'),
                subcort_dict[subc]['id'],
                os.path.join(fsDir, 'mri', 'norm.mgz'), pretessF
            ]
            pretess_cmd = ' '.join(pretess_cmd_tmp)
            os.system(pretess_cmd)

            # Tessellate
            tess_cmd_tmp = [
                'mri_tessellate', pretessF, subcort_dict[subc]['id'], tessF
            ]
            tess_cmd = ' '.join(tess_cmd_tmp)
            os.system(tess_cmd)

            # Smooth
            smooth_cmd_tmp = ['mris_smooth', '-nw', tessF, smoothF]
            smooth_cmd = ' '.join(smooth_cmd_tmp)
            os.system(smooth_cmd)

            # Now reformat it so it's not in the weird QUAD format
            # By default the above freesurfer commands output the mesh in a funky, hard to read file format
            coords, faces = read_geometry(smoothF)
            write_geometry(subcortF, coords, faces)

        shutil.rmtree(tmpDir)
Ejemplo n.º 18
0
def plot_white2pial(input_white,
                    input_pial,
                    adjm,
                    step_size=100,
                    shape="line"):
    """ Plot white to pial

    This function generates lines between corresponding vertices at the white 
    and pial surface to visualize the shift between matched vertices caused by 
    realigning surfaces independently. You can either construct prisms, 
    triangles or lines.    

    Parameters
    ----------
    input_white : str
        Filename of white surface.
    input_pial : str
        Filename of pial surface.
    adjm : obj
        Adjacency matrix.
    step_size : int, optional
        Subset of vertices. The default is 100.
    shape : str, optional
        line, triangle, prism. The default is "line".

    Returns
    -------
    None.

    Notes
    -------
    created by Daniel Haenelt
    Date created: 07-11-2019     
    Last modified: 05-10-2020
    
    """

    # read geometry
    vtx_white, fac_white, header_white = read_geometry(input_white,
                                                       read_metadata=True)
    vtx_pial, fac_pial = read_geometry(input_pial)

    # array containing a list of considered vertices
    t = np.arange(0, len(vtx_white), step_size)

    # initialise faces for specific shape
    if shape == "prism":
        fac_new = [[0, 1, 2], [3, 4, 5], [0, 1, 4], [0, 3, 4], [1, 2, 5],
                   [1, 4, 5], [0, 2, 5], [0, 3, 5]]
        fac_iter = 6
    elif shape == "triangle":
        fac_new = [[0, 1, 2]]
        fac_iter = 3
    elif shape == "line":
        fac_new = [[0, 1, 0]]
        fac_iter = 2

    vtx_res = []
    fac_res = []
    for i in range(len(t)):

        # get index from nearest neighbour of a given vertex
        nn = nn_2d(t[i], adjm, 0)
        nn = nn[:2]

        # get all vertex points for specific shape
        if shape == "prism":
            A = list(vtx_white[t[i]])
            B = list(vtx_white[nn[0]])
            C = list(vtx_white[nn[1]])
            D = list(vtx_pial[t[i]])
            E = list(vtx_pial[nn[0]])
            F = list(vtx_pial[nn[1]])
            vtx_new = [A, B, C, D, E, F]
        elif shape == "triangle":
            A = list(vtx_white[t[i]])
            B = list(vtx_white[nn[0]])
            C = list(vtx_pial[t[i]])
            vtx_new = [A, B, C]
        elif shape == "line":
            A = list(vtx_white[t[i]])
            B = list(vtx_pial[t[i]])
            vtx_new = [A, B]

        # update faces
        if i > 0:
            for j in range(len(fac_new)):
                fac_new[j] = [x + fac_iter for x in fac_new[j]]

        # update resulting vertex and face list
        vtx_res.extend(vtx_new)
        fac_res.extend(fac_new)

    # vertices and faces as array
    vtx_res = np.array(vtx_res)
    fac_res = np.array(fac_res)

    # write output geometry
    write_geometry(input_white + "_plot_white2pial",
                   vtx_res,
                   fac_res,
                   volume_info=header_white)
Ejemplo n.º 19
0
from lib_gbb.normal.get_normal import get_normal

file_surf = "/home/daniel/source/BlenderCBS-master/Haenelt/data/lh.refined_enhanced_inflated"
file_data = "/home/daniel/source/BlenderCBS-master/Haenelt/data/lh.spmT_left_right_GE_EPI2_upsampled_avg_layer4_16.mgh"
file_out = "/home/daniel/source/BlenderCBS-master/Haenelt/data/lh.refined_enhanced_inflated_relief"
scale_factor = 2
""" do not edit below """


def sigmoid(x):
    return 1 / (1 + np.exp(-x))


# load geometry
vtx, fac = read_geometry(file_surf)

# load contrast
data = np.zeros_like(vtx)
data[:, 0] = nb.load(file_data).get_fdata()[:, 0, 0]
data[:, 1] = nb.load(file_data).get_fdata()[:, 0, 0]
data[:, 2] = nb.load(file_data).get_fdata()[:, 0, 0]

# get normals
normal = get_normal(vtx, fac)

# make relief
vtx += scale_factor * sigmoid(data) * normal

# write output
write_geometry(file_out, vtx, fac)
def process_bem(fs_subject_path, bem_path_):
    """
     Main function for BEM extraction.
    """
    subject_ = bem_path_.name.split("_")[0].replace("AVG", "ANTS")

    epi_img_ = nib.load(str(bem_path_))

    for file_name_, discard_inds in zip(
        ["outer_skin.surf", "outer_skull.surf", "inner_skull.surf"],
        [[1, 2, 3, 4], [1, 2, 3], [1, 2]]):
        epi_img_data_ = deepcopy(epi_img_.get_fdata())

        correct_line_artefact(epi_img_data_)

        cond = np.stack([(epi_img_data_ == i_) for i_ in discard_inds]).sum(0)
        epi_img_data_[np.where(cond)] = 1
        epi_img_data_[np.where(np.logical_not(cond))] = 0
        vertices, simplices = measure.marching_cubes_lewiner(
            epi_img_data_, spacing=(1, 1, 1), allow_degenerate=False)[:2]

        path_white = fs_subject_path / subject_ / "surf" / "lh.white"

        try:
            volume_info_ = read_geometry(path_white, read_metadata=True)[2]
        except:
            print("Skipping subject {}...".format(subject_))
            continue

        vertices = vertices @ epi_img_.affine[:3, :
                                              3] + epi_img_.affine[:3,
                                                                   3] - volume_info_[
                                                                       "cras"]

        mesh_ = trimesh.Trimesh(vertices=vertices, faces=simplices)
        trimesh.repair.fix_normals(mesh_, multibody=False)

        smooth_mesh = trimesh.smoothing.filter_laplacian(
            deepcopy(mesh_), lamb=0.8, iterations=15, volume_constraint=True)

        bem_output_path_ = fs_subject_path / subject_ / "bem"
        bem_output_path_.mkdir(parents=True, exist_ok=True)

        vertices, faces_ = smooth_mesh.vertices, smooth_mesh.faces

        # Defect corrections for the large meshes
        vertices, faces_ = fix_all_defects(vertices, faces_)

        # Writing a freesufer mesh file
        file_name_large = file_name_.split(".")[0] + "_large.surf"
        write_geometry(str(bem_output_path_ / file_name_large), vertices,
                       faces_)

        # Writing an obj mesh file
        with (bem_output_path_ /
              file_name_large).with_suffix(".obj").open('w') as file_obj:
            file_obj.write(
                trimesh.exchange.obj.export_obj(
                    trimesh.Trimesh(vertices, faces_)))

        # Decimating BEM surfaces
        vertices, faces_ = decimate_surface(vertices,
                                            faces_,
                                            n_triangles=5120,
                                            method='sphere')

        # Defect correction for decimated meshes...
        vertices, faces_ = fix_all_defects(vertices, faces_)

        # Writing an obj mesh file
        with (bem_output_path_ /
              file_name_).with_suffix(".obj").open('w') as file_obj:
            file_obj.write(
                trimesh.exchange.obj.export_obj(
                    trimesh.Trimesh(vertices, faces_)))

        # Writing a freesufer mesh file
        print("Writing {}...".format(str(bem_output_path_ / file_name_)))
        write_geometry(str(bem_output_path_ / file_name_),
                       vertices,
                       faces_,
                       volume_info=volume_info_)
Ejemplo n.º 21
0
def apply_deformation(vtx,
                      fac,
                      arr_deform,
                      vox2ras_tkr,
                      ras2vox_tkr,
                      path_output="",
                      name_output="",
                      write_output=True):
    """ Apply deformation

    This function applies a coordinate shift to an array of vertices. The 
    coordinate shift is taken from a deformation field where each voxel 
    corresponds to a shift along one direction in voxel space. Vertices outside 
    the deformation field are removed from the mesh.    

    Parameters
    ----------
    vtx : ndarray
        Array of vertices.
    fac : ndarray
        Array of faces.
    arr_deform : ndarray
        4D volume array containing shifts in voxel space.
    vox2ras_tkr : ndarray
        Transformation from voxel to ras space.
    ras2vox_tkr : ndarray
        Transformation from ras to voxel space.
    path_output : str, optional
        Path where output is written. The default is "".
    name_output : str, optional
        Name of output file. The default is "".
    write_output : bool, optional
        Write output file. The default is True.

    Returns
    -------
    vtx : ndarray
        Deformed vertices.
    fac : ndarray
        Corresponding faces.
    ind_keep : ndarray
        Input vertex indices to keep after cleaning.

    Notes
    -------
    created by Daniel Haenelt
    Date created: 28-12-2019
    Last modified: 23-10-2020
    
    """

    # get array dimensions
    xdim = np.shape(arr_deform)[0]
    ydim = np.shape(arr_deform)[1]
    zdim = np.shape(arr_deform)[2]

    # get vertices to voxel space
    vtx = apply_affine(ras2vox_tkr, vtx)

    # only keep vertex indices within the slab
    vtx[vtx[:, 0] < 0, 0] = np.nan
    vtx[vtx[:, 1] < 0, 1] = np.nan
    vtx[vtx[:, 2] < 0, 2] = np.nan

    vtx[vtx[:, 0] > xdim - 1, 0] = np.nan
    vtx[vtx[:, 1] > ydim - 1, 1] = np.nan
    vtx[vtx[:, 2] > zdim - 1, 2] = np.nan

    # remove vertices outside the slab
    if np.any(np.isnan(vtx)):
        ind_keep = np.arange(len(vtx))
        ind_keep = ind_keep[~np.isnan(np.sum(vtx, axis=1))]

        vtx, fac, ind_keep = remove_vertex(vtx, fac, ind_keep)

    # sample shifts from deformation map
    vtx_shift = np.zeros((len(vtx), 3))
    for i in range(3):
        vtx_shift[:, i] = linear_interpolation3d(vtx[:, 0], vtx[:, 1],
                                                 vtx[:, 2], arr_deform[:, :, :,
                                                                       i])
        vtx_shift[np.isnan(vtx_shift[:, i]), i] = 0

    # shift coordinates to new location
    vtx += vtx_shift

    # get new coordinates in ras space
    vtx = apply_affine(vox2ras_tkr, vtx)

    if write_output:
        file_out = os.path.join(path_output, name_output)
        write_geometry(file_out, vtx, fac)
        np.savetxt(file_out + "_ind.txt", ind_keep, fmt="%d")

    return vtx, fac, ind_keep
def fix_intersecting_surfaces(inner_surface_path,
                              outer_surface_path,
                              move_margin=1.5,
                              out_path=None):

    inner_mesh = trimesh.Trimesh(
        *read_geometry(inner_surface_path, read_metadata=False))
    outer_mesh = trimesh.Trimesh(
        *read_geometry(outer_surface_path, read_metadata=False))

    edges = np.array(trimesh.graph.vertex_adjacency_graph(outer_mesh).edges)

    ray_interceptor_inner = trimesh.ray.ray_pyembree.RayMeshIntersector(
        inner_mesh)

    outer_surface_path = Path(outer_surface_path)
    stem = outer_surface_path.stem
    name = outer_surface_path.name
    if out_path is None:
        out_path = outer_surface_path.parent / (stem + suffix +
                                                name[len(stem):])

    out_vertices = deepcopy(outer_mesh.vertices)

    # Instead of using the vertex normal, we take the median of the
    # normal of the neighbor vertex in order for this direction
    # to be more robust to sharp angular changes.
    x = np.concatenate([edges[:, [1, 0]], edges])
    normal = [
        np.median(outer_mesh.vertex_normals[x[x[:, 0] == i][:, 1]], 0)
        for i in np.arange(outer_mesh.vertices.shape[0])
    ]

    # Intersecting surface correction (from outer to inner)
    intersections, inds = ray_interceptor_inner.intersects_location(
        ray_origins=outer_mesh.vertices,
        ray_directions=normal,
        multiple_hits=True)[:2]
    delta1 = np.zeros(out_vertices.shape[0])

    print("inner: ", inner_surface_path)
    print("outer: ", outer_surface_path)

    if len(inds):
        dist = np.sqrt(
            ((outer_mesh.vertices[inds, :] - intersections)**2).sum(axis=1))
        # <5 to avoid picking very long distance points
        inds = inds[dist < 5]
        dist = dist[dist < 5]
        if len(inds):
            msg = "The inner surface intersect the outer surface. Pushing back the outer " + \
                  "surface {} mm out of the inner surface. Saving the outer ".format(move_margin) + \
                  "surface as {}.".format(out_path)
            print(msg)
            # <5 to avoid picking very long distance points
            delta1[inds] = dist + move_margin

    # Intersecting surface correction (from inner to outer)
    delta2 = np.zeros(out_vertices.shape[0])

    closest, distance, triangle_id = trimesh.proximity.closest_point(
        outer_mesh, inner_mesh.vertices)

    normal = (inner_mesh.vertices - closest) / distance[:, None]
    face_normals = outer_mesh.face_normals[triangle_id]

    angles = trimesh.geometry.vector_angle(
        np.stack([normal, face_normals], axis=1))

    new_deltas_df = pd.DataFrame(
        dict(vertex_id=outer_mesh.faces[triangle_id[angles < 1.0]].ravel(),
             delta=np.tile(
                 distance[angles < 1.0] + move_margin,
                 [3, 1]).T.ravel())).groupby("vertex_id").max().reset_index()

    delta2[new_deltas_df.vertex_id] = new_deltas_df.delta

    deltas = np.stack([delta1, delta2]).max(axis=0)

    if np.all(deltas == 0):
        return

    out_vertices += deltas[:, None] * outer_mesh.vertex_normals

    volume_info = read_geometry(outer_surface_path, read_metadata=True)[2]
    write_geometry(out_path,
                   out_vertices,
                   outer_mesh.faces,
                   volume_info=volume_info)
    return deltas
Ejemplo n.º 23
0
 def write(self, surface, surface_path):
     write_geometry(filepath=surface_path,
                    coords=surface.vertices,
                    faces=surface.triangles,
                    volume_info=surface.get_main_metadata())
Ejemplo n.º 24
0
def run_gbb(vtx, fac, vtx_n, ind_control, arr_ref, arr_gradient, arr_vein, 
            arr_ignore, t2s, vox2ras_tkr, ras2vox_tkr, line_dir, line_length, 
            r_size, l_rate, max_iterations, cost_threshold, cost_step=1000, 
            cost_sample=10, path_output = "", show_cost=True, show_slope=False, 
            write_temp=10000, Q0=0, M=0.5, h=1, s=1):
    """ Run GBB

    This function applies the core function of the gbb method.     

    Parameters
    ----------
    vtx : ndarray
        Array of vertex points.
    fac : ndarray
        Array of corresponding faces.
    vtx_n : ndarray
        Array of vertex normals.
    ind_control : ndarray
        Array of vertex indices matching control points.
    arr_ref : ndarray
        Array of reference volume.
    arr_gradient : ndarray
        Array of gradient image.
    arr_vein : ndarray
        Array of vein mask.
    arr_ignore : ndarray
        Array of ignore mask.
    t2s : bool
        Image contrast.
    vox2ras_tkr : ndarray
        Transformation matrix from voxel space to ras.
    ras2vox_tkr : ndarray
        Transformation matrix from ras to voxel space.
    line_dir : int
        Line axis in ras convention (0,1,2,3).
    line_length : float
        Line length in one direction in mm.
    r_size : list
        Neighborhood radius in mm.
    l_rate : list
        Learning rate.
    max_iterations : list
        Maximum iterations.
    cost_threshold : list
        Cost function threshold.
    cost_step : int, optional
        Step size between cost array points. The default is 1000.
    cost_sample : int, optional
        Sample size for linear fit. The default is 10.
    path_output : str, optional
        Path where intermediate folder is created. The default is "".
    show_cost : bool, optional
        Show temporary cost function. The default is True.
    show_slope : bool, optional
        Show temporary slope function. The default is False.
    write_temp : int, optional
        Step size to write intermediate surfaces (if set > 0). The default is 
        10000.
    Q0 : float, optional
        Const function offset parameter. The default is 0.
    M : float, optional
        Cost function slope parameter. The default is 0.5.
    h : float, optional
        Cost function weight parameter. The default is 1.
    s : float, optional
        Cost function distance parameter. The default is 1.

    Returns
    -------
    vtx : ndarray
        Shifted array of vertex points.
    gbb : dict
        Collection of performance parameters.

    Notes
    -------
    created by Daniel Haenelt
    Date created: 06-02-2020          
    Last modified: 21-10-2020

    """
    
    # make intermediate folder
    if write_temp > 0 and path_output:
        tmp = np.random.randint(0, 10, 5)
        tmp_string = ''.join(str(i) for i in tmp)
        path_temp = os.path.join(path_output,"tmp_"+tmp_string)
        if not os.path.exists(path_temp):
            os.makedirs(path_temp)
    
    # get vertices to ignore
    if arr_ignore is not None:
        _, ind_ignore = get_ignore(vtx, arr_ignore, ras2vox_tkr, write_output=False, path_output=False)
    else:
        ind_ignore = []
    
    print("start mesh initialization (gbb)")    
    
    # run surface reginement
    i = 0
    j = 0
    p = 0
    q = 0
    counter = 0
    step = 0
    cost_array = []
    m_array = []
    n_array = [] 
    n_iter_step = []
    n_coords = len(vtx)
    while True:
        
        # get current vertex point
        n_vertex = np.random.randint(n_coords)
        if n_vertex in ind_ignore:
            counter += 1
            continue
        
        # get shift        
        vtx_shift = get_shift(vtx, fac, vtx_n, n_vertex, arr_gradient, 
                              vox2ras_tkr, ras2vox_tkr, arr_vein, line_dir, 
                              line_length, t2s, False)
        
        # update mesh
        if len(vtx_shift):
            nn_ind, _ = nn_3d(vtx[n_vertex], vtx, r_size[step])
            if np.any(np.in1d(nn_ind,ind_control)):
                counter += 1
                continue     
            vtx = update_mesh(vtx,vtx_shift,n_vertex,nn_ind,l_rate[step])
        else:
            counter += 1
            continue        
        
        # get cost function
        if p >= cost_step:
            J = cost_BBR(vtx, vtx_n, arr_ref, ras2vox_tkr, Q0=Q0, M=M, h=h, s=s, t2s=t2s)
            cost_array.append(J)
            q += 1
            p = 0
            if len(cost_array) >= cost_sample:
                
                # check exit criterion
                m_fit, n_fit, exit_crit = check_exit(np.arange(q-cost_sample,q), 
                                                     cost_array[-cost_sample:], 
                                                     cost_threshold=cost_threshold[step])
                
                # save computed slope and y-axis intercept of liner fit
                m_array.append(m_fit)
                n_array.append(n_fit)
                
                # shot plots
                if show_cost:
                    set_title = "Exit criterion: "+str(exit_crit)+", Step: "+str(step)
                    plot_cost(q, cost_array, m_fit, n_fit, set_title, save_plot=False, 
                              path_output=False, name_output=False)
                    
                if show_slope:
                    set_title = "Exit criterion: "+str(exit_crit)+", Step: "+str(step)
                    plot_slope(q, m_array, set_title, save_plot=False, path_output=False, 
                               name_output=False)
        else:
            exit_crit = False
    
        # check exit
        if exit_crit and step < len(max_iterations) - 1 or j == max_iterations[step]:
            n_iter_step.append(j)
            step += 1
            j = 0
            print("start registration step "+str(step)+" at iteration "+str(i))
        elif exit_crit and step == len(max_iterations) - 1:
            n_iter_step.append(j)
            gbb_converged = True
            print("registration converged!")   
            break
        elif step == len(max_iterations) - 1 and j == max_iterations[-1]:
            n_iter_step.append(j)
            gbb_converged = False
            print("registration did not converge!")
            break
    
        # write intermediate surfaces
        if write_temp and path_output and not np.mod(i,write_temp):
            write_geometry(os.path.join(path_temp,"temp_"+str(i)), vtx, fac)
        
        i += 1
        j += 1
        p += 1
    
    # print some information
    print("final number of iterations: "+str(i))
    print("final number of skipped iterations: "+str(counter))
    
    # collect some descriptive variables
    gbb = dict()
    gbb["convergence"] = gbb_converged
    gbb["cost_array"] = cost_array
    gbb["m_array"] = m_array
    gbb["n_array"] = n_array
    gbb["n_iter"] = i
    gbb["n_skip"] = counter
    gbb["n_iter_step"] = n_iter_step
    
    return vtx, gbb
Ejemplo n.º 25
0
def make_mesh(boundary_in,
              ref_in,
              file_out,
              nlayer,
              flip_faces=False,
              niter_smooth=2,
              niter_upsample=0,
              niter_inflate=15):
    """
    This function generates a surface mesh from a levelset image. The surface mesh is smoothed and a
    curvature file is generated. Vertices are in the vertex ras coordinate system. Optionally, the
    mesh can be upsampled and an inflated version of the mesh can be written out. The hemisphere
    has to be indicated as prefix in the output file. If nlayer is set to -1, a 3D levelset image
    can be used as boundary input file.
    Inputs:
        *boundary_in: filename of 4D levelset image.
        *ref_in: filename of reference volume for getting the coordinate transformation.
        *file_out: filename of output surface.
        *nlayer: layer from the 4D boundary input at which the mesh is generated.
        *flip_faces: reverse normal direction of mesh.
        *niter_smooth: number of smoothing iterations.
        *niter_upsample: number of upsampling iterations (is performed if set > 0).
        *niter_inflate: number of inflating iterations (is performed if set > 0).
    
    created by Daniel Haenelt
    Date created: 18-12-2019
    Last modified: 24-07-2020
    """
    import os
    import numpy as np
    import nibabel as nb
    from nibabel.affines import apply_affine
    from nibabel.freesurfer.io import write_geometry
    from nighres.surface import levelset_to_mesh
    from cortex.polyutils import Surface
    from lib.surface.vox2ras import vox2ras
    from lib.surface.smooth_surface import smooth_surface
    from lib.surface.upsample_surf_mesh import upsample_surf_mesh
    from lib.surface.get_curvature import get_curvature
    from lib.surface import inflate_surf_mesh

    # make output folder
    if not os.path.exists(os.path.dirname(file_out)):
        os.makedirs(os.path.dirname(file_out))

    # get levelset boundary from single layer
    boundary = nb.load(boundary_in)
    boundary.header["dim"][0] = 1
    boundary_array = boundary.get_fdata()

    if nlayer != -1:
        boundary_array = boundary_array[:, :, :, nlayer]

    boundary = nb.Nifti1Image(boundary_array, boundary.affine, boundary.header)

    # make mesh
    surf = levelset_to_mesh(boundary,
                            connectivity="18/6",
                            level=0.0,
                            inclusive=True)

    # get vertices and faces
    vtx = surf["result"]["points"]
    fac = surf["result"]["faces"]

    # get vox2ras transformation
    vox2ras_tkr, _ = vox2ras(ref_in)

    # apply vox2ras to vertices
    vtx = apply_affine(vox2ras_tkr, vtx)

    # flip faces
    if flip_faces:
        fac = np.flip(fac, axis=1)

    # write mesh
    write_geometry(file_out, vtx, fac)

    # smooth surface
    smooth_surface(file_out, file_out, niter_smooth)

    # upsample mesh (optionally)
    if niter_upsample != 0:
        upsample_surf_mesh(file_out, file_out, niter_upsample, "linear")

    # print number of vertices and average edge length
    print("number of vertices: " + str(len(vtx[:, 0])))
    print("average edge length: " + str(Surface(vtx, fac).avg_edge_length))

    # get curvature (looks for hemisphere prefix)
    get_curvature(file_out, os.path.dirname(file_out))

    # inflate surface (optionally)
    if niter_inflate != 0:
        inflate_surf_mesh(file_out, file_out + "_inflated", niter_inflate)
Ejemplo n.º 26
0
def make_sphere(file_in, file_out, n_inflate=100, radius=None):
    """
    The scripts takes a generated FreeSurfer mesh and transformes it into
    a sphere with defined radius.
    Inputs:
        *file_in: filename of input surface.
        *file_out: filename of output surface.
        *n_inflated: number of inflating iterations (if > 0).
        *radius: radius of final sphere in mm (if not None).

    created by Daniel Haenelt
    Date created: 26-08-2020       
    Last modified: 26-08-2020
    """
    import os
    import sys
    import subprocess
    import numpy as np
    from shutil import copyfile
    from nibabel.freesurfer.io import read_geometry, write_geometry
    from lib.io.get_filename import get_filename
    from lib.surface.inflate_surf_mesh import inflate_surf_mesh

    def cart2pol(x, y, z):
        r = np.sqrt(x**2 + y**2 + z**2)
        phi = np.arctan2(y, x)
        theta = np.arccos(z / r)
        return r, phi, theta

    def pol2cart(r, phi, theta):
        x = r * np.sin(theta) * np.cos(phi)
        y = r * np.sin(theta) * np.sin(phi)
        z = r * np.cos(theta)
        return x, y, z

    # make output folder
    path_output, _, _ = get_filename(file_out)
    if not os.path.exists(path_output):
        os.makedirs(path_output)

    # temporary file
    tmp = np.random.randint(0, 10, 5)
    tmp_string = ''.join(str(i) for i in tmp)
    file_tmp = os.path.join(path_output, tmp_string)

    # inflate surface mesh
    if n_inflate:
        inflate_surf_mesh(file_in, file_tmp, n_inflate)
    else:
        copyfile(file_in, file_tmp)

    # inflate surface
    try:
        subprocess.run(['mris_sphere', '-q', file_tmp, file_out], check=True)
    except subprocess.CalledProcessError:
        sys.exit("Sphere computation failed!")

    # change radius
    if radius:
        vtx, fac = read_geometry(file_out)
        r, phi, theta = cart2pol(vtx[:, 0], vtx[:, 1], vtx[:, 2])
        r[:] = radius
        vtx[:, 0], vtx[:, 1], vtx[:, 2] = pol2cart(r, phi, theta)
        write_geometry(file_out, vtx, fac)

    # remove temporary file
    os.remove(file_tmp)
Ejemplo n.º 27
0
def run_devein(vtx,
               fac,
               vtx_norm,
               arr_vein,
               arr_ignore,
               adjm,
               ras2vox_tkr,
               n_neighbor=20,
               line_dir=2,
               smooth_iter=30,
               max_iterations=1000):
    """ Run devein

    This function finds vertex points which are located within marked veins and 
    shift these and their neighborhood until the mesh is free of trapped 
    vertices (or the maximum number of iterations is reached). Shifts are 
    computed by averaging vertex coordinates within a neighborhood around a 
    single vertex along one direction or along all axes. All shifts are applied 
    in inward direction. Optionally, the output mesh can be smoothed.    

    Parameters
    ----------
    vtx : ndarray
        Array of vertex points.
    fac : ndarray
        Array of corresponding faces.
    vtx_norm : ndarray
        Array of corresponding vertex normals.
    arr_vein : ndarray
        Vein mask.
    arr_ignore : ndarray
        Binary mask where deveining is omitted.
    adjm : obj
        Adjacecy matrix.
    ras2vox_tkr : ndarray
        Transformation matrix from ras to voxel space.
    n_neighbor : int, optional
        Neighborhood size. The default is 20.
    line_dir : int, optional
        Shift direction in ras conventions (0,1,2,3). The default is 2.
    smooth_iter : int, optional
        Number of smoothing iterations of final mesh. The default is 30.
    max_iterations : int, optional
        Maximum number of deveining iterations. The default is 1000.

    Returns
    -------
    vtx : ndarray
        Shifted array of vertex points.
    counter : int
        Number of deveining iterations.

    Notes
    -------
    created by Daniel Haenelt
    Date created: 06-02-2020             
    Last modified: 08-10-2020 

    """

    # fix parameters
    line_threshold = 0.05  # if direction is along one axis, omit line if length is below threshold

    # check line direction
    if line_dir > 3 or line_dir < 0:
        sys.exit("error: choose a valid line direction!")

    # check vein array
    if arr_vein is None:
        sys.exit("error: no vein mask found for deveining!")

    # load arrays
    arr_vein = np.round(arr_vein).astype(int)

    # get image dimensions
    xdim = np.shape(arr_vein)[0]
    ydim = np.shape(arr_vein)[1]
    zdim = np.shape(arr_vein)[2]

    if arr_ignore is not None:
        arr_ignore = np.round(arr_ignore).astype(int)
        arr_vein[arr_ignore == 1] = 0

    # centroid
    vtx_c = np.mean(vtx, axis=0)

    # get nearest voxel coordinates
    vtx_vox = apply_affine(ras2vox_tkr, vtx)
    vtx_vox = np.round(vtx_vox).astype(int)

    # mask outlier points (ignored in deveining)
    vtx_mask = np.ones(len(vtx_vox))

    vtx_mask[vtx_vox[:, 0] > xdim - 1] = 0
    vtx_mask[vtx_vox[:, 1] > ydim - 1] = 0
    vtx_mask[vtx_vox[:, 2] > zdim - 1] = 0

    vtx_mask[vtx_vox[:, 0] < 0] = 0
    vtx_mask[vtx_vox[:, 1] < 0] = 0
    vtx_mask[vtx_vox[:, 2] < 0] = 0

    # get vertices trapped in veins
    vein_mask = arr_vein[vtx_vox[:, 0], vtx_vox[:, 1], vtx_vox[:,
                                                               2]] * vtx_mask
    n_veins = len(vein_mask[vein_mask == 1])

    print("start mesh initialization (deveining)")

    # loop through vertices
    counter = 0
    while n_veins > 0 and counter < max_iterations:

        # print current status
        print("i: " + str(counter) + ", # of trapped vertices: " +
              str(len(vein_mask[vein_mask == 1])))

        # select random vein vertex
        vein_ind = np.where(vein_mask == 1)[0]
        curr_ind = vein_ind[np.random.randint(len(vein_ind))]
        nn_ind = nn_2d(curr_ind, adjm, n_neighbor)

        # get shift as weighted average
        if line_dir == 3:
            vtx_shift = vtx[nn_ind, :].copy()
            vtx_shift = vtx[curr_ind, :] - vtx_shift
        else:
            vtx_shift = np.zeros((len(nn_ind), 3))
            vtx_shift[:, line_dir] = vtx[nn_ind, line_dir]
            vtx_shift[:, line_dir] = vtx[curr_ind,
                                         line_dir] - vtx_shift[:, line_dir]

        vtx_shift = np.mean(vtx_shift, axis=0)

        # check inward shift by comparing distance to centroid
        vtx_dist_noshift = norm(vtx[curr_ind, :] - vtx_c)
        vtx_dist_shift = norm(vtx[curr_ind, :] - vtx_shift - vtx_c)

        # do only inward shifts
        if vtx_dist_shift - vtx_dist_noshift > 0:
            vtx_shift = -1 * vtx_shift

        # update mesh if valid inward shift
        if line_dir != 3 and np.abs(vtx_norm[curr_ind,
                                             line_dir]) < line_threshold:
            vtx_temp_vox = apply_affine(ras2vox_tkr, vtx[curr_ind])
            vtx_temp_vox = np.round(vtx_temp_vox).astype(int)
            arr_vein[vtx_temp_vox[0], vtx_temp_vox[1], vtx_temp_vox[2]] = 0
        else:
            vtx = update_mesh(vtx, vtx_shift, curr_ind, nn_ind, 1)
            vtx_vox = apply_affine(ras2vox_tkr, vtx)
            vtx_vox = np.round(vtx_vox).astype(int)

        # get all vertices within vein
        vein_mask = np.zeros(len(vtx))
        vein_mask = arr_vein[vtx_vox[:, 0], vtx_vox[:, 1],
                             vtx_vox[:, 2]] * vtx_mask
        n_veins = len(vein_mask[vein_mask == 1])

        counter += 1

    if counter < max_iterations:
        print("deveining converged!")

    # smooth output
    if smooth_iter:
        tmp = np.random.randint(0, 10, 5)
        tmp_string = ''.join(str(i) for i in tmp)
        surf_temp = os.path.join(os.getcwd(), "surf_" + tmp_string)
        write_geometry(surf_temp, vtx, fac)
        smooth_surface(surf_temp, surf_temp, smooth_iter)
        vtx, _ = read_geometry(surf_temp)
        os.remove(surf_temp)

    return vtx, counter
def register_retinotopy_command(args):
    '''
    register_retinotopy_command(args) can be given a list of arguments, such as sys.argv[1:]; these
    arguments may include any options and must include at least one subject id. All subjects whose
    ids are given are registered to a retinotopy model, and the resulting registration, as well as
    the predictions made by the model in the registration, are exported.
    '''
    # Parse the arguments
    (args, opts) = _retinotopy_parser(args)
    # First, help?
    if opts['help']:
        print register_retinotopy_help
        return 1
    # and if we are verbose, lets setup a note function
    verbose = opts['verbose']

    def note(s):
        if verbose: print s
        return verbose

    # Add the subjects directory, if there is one
    if 'subjects_dir' in opts and opts['subjects_dir'] is not None:
        add_subject_path(opts['subjects_dir'])
    # Parse the simple numbers
    for o in [
            'weight_cutoff', 'edge_strength', 'angle_strength',
            'func_strength', 'max_step_size', 'max_out_eccen'
    ]:
        opts[o] = float(opts[o])
    opts['max_steps'] = int(opts['max_steps'])
    # These are for now not supported: #TODO
    if opts['angle_math'] or opts['angle_radians'] or opts['eccen_radians']:
        print 'Mathematical angles and angles not in degrees are not yet supported.'
        return 1
    # The remainder of the args can wait for now; walk through the subjects:
    tag_key = {
        'eccen': 'eccentricity',
        'angle': 'polar_angle',
        'label': 'visual_area'
    }
    for subnm in args:
        sub = freesurfer_subject(subnm)
        note('Processing subject: %s' % sub.id)
        # we need to register this subject...
        res = {}
        ow = not opts['no_overwrite']
        for h in ['LH', 'RH']:
            note('   Processing hemisphere: %s' % h)
            hemi = sub.__getattr__(h)
            # See if we are loading custom values...
            (ang, ecc, wgt) = (None, None, None)
            suffix = '_' + h.lower() + '_file'
            if opts['angle' + suffix] is not None:
                ang = _guess_surf_file(opts['angle' + suffix])
            if opts['eccen' + suffix] is not None:
                ecc = _guess_surf_file(opts['eccen' + suffix])
            if opts['weight' + suffix] is not None:
                wgt = _guess_surf_file(opts['weight' + suffix])
            # Do the registration
            note('    - Running Registration...')
            res[h] = register_retinotopy(
                hemi,
                retinotopy_model(),
                polar_angle=ang,
                eccentricity=ecc,
                weight=wgt,
                weight_cutoff=opts['weight_cutoff'],
                partial_voluming_correction=opts['part_vol_correct'],
                edge_scale=opts['edge_strength'],
                angle_scale=opts['angle_strength'],
                functional_scale=opts['func_strength'],
                prior=opts['prior'],
                max_predicted_eccen=opts['max_out_eccen'],
                max_steps=opts['max_steps'],
                max_step_size=opts['max_step_size'])
            # Perform the hemi-specific outputs now:
            if not opts['no_reg_export']:
                regnm = '.'.join(
                    [h.lower(), opts['registration_name'], 'sphere', 'reg'])
                flnm = (os.path.join(sub.directory, 'surf', regnm)
                        if h == 'LH' else os.path.join(sub.directory, 'xhemi',
                                                       'surf', regnm))
                if ow or not os.path.exist(flnm):
                    note('    - Exporting registration file: %s' % flnm)
                    fsio.write_geometry(
                        flnm, res[h].coordinates.T, res[h].faces.T,
                        'Created by neuropythy (github.com/noahbenson/neuropythy)'
                    )
                else:
                    note('    - Skipping registration file: %s (file exists)' %
                         flnm)
            if not opts['no_surf_export']:
                for dim in ['angle', 'eccen', 'label']:
                    flnm = os.path.join(
                        sub.directory, 'surf',
                        '.'.join([h.lower(), opts[dim + '_tag'], 'mgz']))
                    if ow or not os.path.exist(flnm):
                        note('    - Exporting prediction file: %s' % flnm)
                        img = fsmgh.MGHImage(
                            np.asarray([[res[h].prop(tag_key[dim])]],
                                       dtype=(np.int32 if dim == 'label' else
                                              np.float32)), np.eye(4))
                        img.to_filename(flnm)
                    else:
                        note('    - Skipping prediction file: %s (file exists)'
                             % flnm)
        # Do the volume exports here
        if not opts['no_vol_export']:
            note('   Processing volume data...')
            note('    - Calculating cortex-to-ribbon mapping...')
            surf2rib = cortex_to_ribbon_map(sub, hemi=None)
            for dim in ['angle', 'eccen', 'label']:
                flnm = os.path.join(sub.directory, 'mri',
                                    opts[dim + '_tag'] + '.mgz')
                if ow or not os.path.exist(flnm):
                    note('    - Generating volume file: %s' % flnm)
                    vol = cortex_to_ribbon(
                        sub, (res['LH'].prop(tag_key[dim]), res['RH'].prop(
                            tag_key[dim])),
                        map=surf2rib,
                        method=('max' if dim == 'label' else 'weighted'),
                        dtype=(np.int32 if dim == 'label' else np.float32))
                    note('    - Exporting volume file: %s' % flnm)
                    vol.to_filename(flnm)
                else:
                    note('    - Skipping volume file: %s (file exists)' % flnm)
        # That is it for this subject!
        note('   Subject %s finished!' % sub.id)
    # And if we made it here, all was successful.
    return 0