Beispiel #1
0
def calculate_area(filename_surf, filename_area=""):
    """
    The function calculates vertex-wise surface area. The code is taken from the octave code 
    surf2area.m from Anderson Winkler found in his github repository (https://github.com/
    andersonwinkler/areal). Consider a triangular face ABC with corner points
    a = [x_A, y_A, z_A]'
    b = [x_B, y_B, z_B]'
    c = [x_C, y_C, z_C]'
    The area for this triangle is given by the normed cross product A = |u x v|/2 with u = a - c and 
    v = b - c. This is a face-wise surface area representation. To convert this to a vertex-wise 
    representation, we assign each vertex one third of the sum of the areas of all faces that meet 
    at that vertex. Cf. Anderson Winkler et al. Measuring and comparing brain cortical surface area 
    and other areal quantities, Neuroimage 61(4), p. 1428-1443 (2012).
    Inputs:
        *file_surf: input file geometry on which surface area is calculated.
        *file_area: file name of the surface area file.
    Outputs:
        *dpv: vertex-wise surface area.
        
    created by Daniel Haenelt
    Date created: 01-11-2018             
    Last modified: 17-12-2018
    """
    import numpy as np
    from numpy.linalg import norm
    from nibabel.freesurfer.io import write_morph_data, read_geometry

    # Read the surface file
    vtx, fac = read_geometry(filename_surf)
    nV = len(vtx)
    nF = len(fac)

    # compute area per face (DPF)
    facvtx = np.concatenate([vtx[fac[:, 0]], vtx[fac[:, 1]], vtx[fac[:, 2]]],
                            axis=1)
    facvtx0 = facvtx[:, 0:6] - np.concatenate(
        [facvtx[:, 6:9], facvtx[:, 6:9]], axis=1)  # place 3rd vtx at origin
    cp = np.cross(facvtx0[:, 0:3], facvtx0[:, 3:6], axisa=1,
                  axisb=1)  # cross product
    dpf = norm(cp, axis=1) / 2  # half of the norm
    print("Total area (facewise): " + str(np.sum(dpf)))

    # compute area per vertex (DPV)
    dpv = np.zeros(nV)

    # for speed, divide the dpf by 3
    dpf = dpf / 3

    # redistribute
    for f in range(nF):
        dpv[fac[f, :]] = dpv[fac[f, :]] + dpf[f]

    print("Total area (vertexwise): " + str(np.sum(dpv)))

    # save dpv
    if filename_area:
        write_morph_data(filename_area, dpv)

    # return vertex-wise surface area
    return dpv
Beispiel #2
0
def morph2dense(source_sphere,target_sphere,input_morph,path_output):
    """
    This function maps a morphological file from a source surface to a target target surface.    
    Inputs:
        *source_sphere: source surface.
        *target_sphere: target surface.
        *input_morph: morphological input file.
        *path_output: path where output is saved.
        
    created by Daniel Haenelt
    Date created: 13-07-2019
    Last modified: 13-07-2019
    """
    import os
    from nibabel.freesurfer.io import read_morph_data, write_morph_data, read_geometry
    from scipy.interpolate import griddata    

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

    # transform morphological data to dense surfaces
    pts_sphere_dense, _ = read_geometry(target_sphere)
    pts_sphere, _ = read_geometry(source_sphere)
    
    # get morphological data
    morph = read_morph_data(input_morph)
    
    # do the transformation
    method = "nearest"
    morph_dense = griddata(pts_sphere, morph, pts_sphere_dense, method)
        
    # write dense morphological data
    write_morph_data(os.path.join(path_output,os.path.basename(input_morph)), morph_dense)
Beispiel #3
0
def save_freesurfer_morph(filename, obj, face_count=0):
    '''
    save_freesurfer_morph(filename, obj) saves the given object using nibabel.freesurfer.io's
      write_morph_data function, and returns the given filename.
    '''
    fsio.write_morph_data(filename, obj, fnum=face_count)
    return filename
def create_R2(in_vol, in_rh_surf, in_lh_surf):
  
  img = nib.load(in_vol)
  data = img.get_fdata()

  # set all visual areas 1 through 12 to 1.0  
  for i in range(data.shape[0]):
    for j in range(data.shape[1]):
      for k in range(data.shape[2]):
        if data[i,j,k] > 0.0:
          data[i,j,k] = 1.0
  out_vol = nib.Nifti1Image(data, img.affine)
  nib.save(out_vol, os.path.join(os.getcwd(),'prf','r2.nii.gz'))

  data = fsio.read_morph_data(in_rh_surf)
  for i in range(data.shape[0]):
    if data[i] > 0.0:
      data[i] = 1.0
  fsio.write_morph_data(os.path.join(os.getcwd(),'prf','prf_surfaces','rh.r2'),data)

  data = fsio.read_morph_data(in_lh_surf)
  for i in range(data.shape[0]):
    if data[i] > 0.0:
      data[i] = 1.0
  fsio.write_morph_data(os.path.join(os.getcwd(),'prf','prf_surfaces','lh.r2'),data)
def get_b0_orientation(surf_in,
                       vol_in,
                       write_output=False,
                       path_output="",
                       name_output=""):
    """
    This function computes the angle between surface normals and B0-direction per vertex.
    Inputs:
        *surf_in: input of surface mesh.
        *vol_in: input of corresponding nifti volume.
        *write output: write out to disk (boolean).
        *path_output: path where to save output.
        *name_output: basename of output file.
    Outputs:
        *theta: angle in radians.
        
    created by Daniel Haenelt
    Date created: 31-07-2020 
    Last modified: 31-07-2020
    """
    import os
    import numpy as np
    import nibabel as nb
    from nibabel.affines import apply_affine
    from nibabel.freesurfer.io import read_geometry, write_morph_data
    from lib.io.get_filename import get_filename
    from lib.surface.vox2ras import vox2ras
    from lib_gbb.normal import get_normal

    # make subfolders
    if write_output and not os.path.exists(path_output):
        os.makedirs(path_output)

    # get hemi from surface filename
    _, hemi, _ = get_filename(surf_in)

    # load surface
    vtx, fac = read_geometry(surf_in)

    # get transformation matrix
    _, r2v = vox2ras(vol_in)  # ras-tkr -> voxel
    v2s = nb.load(vol_in).affine  # voxel -> scanner-ras
    M = v2s.dot(r2v)

    # apply affine transformation
    vtx = apply_affine(M, vtx)

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

    # get angle between b0 and surface normals in radians
    theta = np.arccos(np.dot(n, [0, 0, 1]))

    # write output
    if write_output:
        write_morph_data(os.path.join(path_output, hemi + "." + name_output),
                         theta)

    return theta
Beispiel #6
0
def project_results_annot(annot, results_file, file_like):
    results = pd.read_csv(results_file, sep = ',')
    print(results)
    annot_info = read_annot(annot)
    values = annot_info[0]
    output = values.astype(np.float32)*0
    labels = np.array(annot_info[2])
    for index, row in results.iterrows():
        w = np.where(labels == row.label.encode('utf-8'))[0]
        if (len(w) > 0):            
            output[values == w] = row.value
            print(row.label, w, row.value)
        
    write_morph_data(file_like = file_like, values = output)
Beispiel #7
0
def write_surface(image, filename):

    extension = filename.split('.')[-1]

    if extension == 'mha':
        sitk.WriteImage(image, filename)

    elif extension == 'annot':
        print(type(image[1]))
        io.write_annot(filename, image[0], image[1], image[2])

    elif extension == 'label':
        raise ValueError('Reader for extensions \'label\' not yet implemented')

    elif extension in ['inflated', 'pial', 'white']:
        io.read_geometry(filename, image[0], image[1])

    else:
        return io.write_morph_data(filename, image)
 def export(flnm, p):
     fsio.write_morph_data(flnm, p)
     return flnm
def compare_editors(subjects_dir, outdir, subject, editor1, editor2, hemi,
                    surfname):

    subj_dir1 = os.path.join(subjects_dir, '{}-{}'.format(subject, editor1))
    subj_dir2 = os.path.join(subjects_dir, '{}-{}'.format(subject, editor2))

    # Construct surface paths
    surf1_fname = os.path.join(subj_dir1, 'surf',
                               '{}.{}'.format(hemi, surfname))
    surf2_fname = os.path.join(subj_dir2, 'surf',
                               '{}.{}'.format(hemi, surfname))

    # Init Hausdorff distance return values
    d12, d21, dsym = np.nan, np.nan, np.nan

    # Init continuation flag
    keep_going = True
    coords1 = np.zeros([
        1,
    ])
    coords2 = np.zeros([
        1,
    ])

    if not os.path.isfile(surf1_fname):
        print('* Subject 1 surface file {} does not exist - exiting'.format(
            surf1_fname))
        keep_going = False

    if not os.path.isfile(surf2_fname):
        print('* Subject 2 surface file {} does not exist - exiting'.format(
            surf2_fname))
        keep_going = False

    # Load surfaces
    try:
        coords1, faces1 = read_geometry(surf1_fname)
    except IOError:
        print('* Problem loading surface from {}'.format(surf1_fname))
        keep_going = False

    try:
        coords2, faces2 = read_geometry(surf2_fname)
    except IOError:
        print('* Problem loading surface from {}'.format(surf2_fname))
        keep_going = False

    if keep_going:

        print('{}-{}-{}-{} mesh has {} points'.format(subject, editor1, hemi,
                                                      surfname,
                                                      coords1.shape[0]))
        print('{}-{}-{}-{} mesh has {} points'.format(subject, editor2, hemi,
                                                      surfname,
                                                      coords2.shape[0]))

        # Fast pairwise Euclidean distances between nodes of surface 1 and 2
        # If coords1 is N x 3 and coords2 is M x 3, distmin is N x M
        print('Computing pairwise distances ({} to {})'.format(
            editor1, editor2))
        t0 = dt.now()
        _, dmin12 = pairwise_distances_argmin_min(coords1, coords2)
        delta = dt.now() - t0
        print('Done in {:0.3f} seconds'.format(delta.total_seconds()))

        # Calculate forward Hausdorff distance from pairwise distance results
        d12 = np.max(dmin12)

        # Fast Hausdorff distances between nodes of surface 1 and 2
        print('Computing Fast Hausdorff Distances')
        t0 = dt.now()
        d21, _, _ = directed_hausdorff(coords2, coords1)
        delta = dt.now() - t0
        print('Done in {:0.3f} seconds'.format(delta.total_seconds()))

        # Symmetric Hausdorff distance (max(d12, d21))
        dsym = max(d12, d21)

        print('Forward Hausdorff Distance   : {:0.3f} mm'.format(d12))
        print('Reverse Hausdorff Distance   : {:0.3f} mm'.format(d21))
        print('Symmetric Hausdorff Distance : {:0.3f} mm'.format(dsym))

        # Save closest distances as a morphometry/curv file
        dist_fname = os.path.join(
            outdir, '{}-{}-{}-{}-{}.dist'.format(subject, editor1, editor2,
                                                 hemi, surfname))
        print('Saving intersurface distances to {}'.format(dist_fname))
        write_morph_data(dist_fname, dmin12)

        # Copy subject 1 surface to output directory for use with distance annotation in Freeview
        surf1_bname = os.path.basename(surf1_fname)
        surf1_outname = '{}-{}-{}'.format(subject, editor1, surf1_bname)
        print('Copying {} to {}'.format(surf1_bname, surf1_outname))
        shutil.copy(surf1_fname, os.path.join(outdir, surf1_outname))

    return subject, editor1, editor2, hemi, surfname, d12, d21, dsym
Beispiel #10
0
        sulc      = transfertodense(subjectid, a3,          hemi,'nearest')
      #  save(sprintf('%s/%smidgrayDENSE.pkl',dir0,hemi),'thickness','curvature')

        # write mgz
        writemgz(subjectid,'thicknessDENSE',thickness, hemi)
        writemgz(subjectid,'curvatureDENSE',curvature, hemi)
        writemgz(subjectid,'sulcDENSE',     sulc,      hemi)

        # write mgz for truncated
        writemgz(subjectid,f'thicknessDENSETRUNC{fstruncate}',thickness[a2['validix']],hemi)
        writemgz(subjectid,f'curvatureDENSETRUNC{fstruncate}',curvature[a2['validix']],hemi)
        writemgz(subjectid,f'sulcDENSETRUNC{fstruncate}',     sulc[a2['validix']],     hemi)

        # write curv
        _,facesA = fsio.read_geometry((Path(fsdir)/'surf'/f'{hemi}.inflatedDENSE').str)
        fsio.write_morph_data((Path(fsdir)/'surf'/f'{hemi}DENSE.curv').str,curvature,facesA.shape[0])

        # write curv for truncated
        _,facesA = fsio.read_geometry((Path(fsdir)/'surf'/f'{hemi}.inflatedDENSETRUNC{fstruncate}').str)
        fsio.write_morph_data((Path(fsdir)/'surf'/f'{hemi}DENSETRUNC{fstruncate}.curv').str, curvature[a2['validix']], facesA.shape[0])


    ######## compute and save some useful quantities for dense trunc surfaces

    # for each hemisphere
    for hemi in hemis:

        # for each of the surfaces
        for sf in surfs:

            # read in the surface
def calculate_distortion(file_patch, file_white, path_output, hemi):
    """
    This script computes two metrics (areal distortion and line distortion) to estimate the amount 
    of distortion in the flattening process. The form of the metric is similar to 
    http://brainvis.wustl.edu/wiki/index.php/Caret:Operations/Morphing
    
    For vertex-wise areal distortion (VAD), first all faces which lie within the patch are searched. 
    Then, triangle areas before and after flattening are computed for each face. The amount of 
    distortion is defined as ratio between both areas. A vertex-wise representation is computed by 
    considereing each vertex point within the patch and taking the sum of areal distortions of all
    neighbouring faces.
    
    For vertex-wise line distortion (VLD), for each node in the patch, we take the vertex points 
    before and after flattening and look for all neighbouring nodes, i.e., we are looking for all 
    faces which contain the node. We take then the sum of all euclidean distances to all 
    neighbouring nodes and before and after flattening separately and compute the ratio between 
    summed distances.
    
    Note that only points within the patch are taken into account which have full faces within the 
    point cloud of the patch corresponding to the original white surface. I.e., a few border points 
    are excluded from the analysis and set to zero in the morphological output file. The number of 
    excluded points is returned for each metric.   
    Inputs:
        *file_patch: filename of flattened patch.
        *file_white: filename of white surface.
        *path_output: path where output is written.
        *hemi: hemisphere.
    Outputs:
        *VAD_params: Descriptive parameters of areal distortion.
        *VLD_paramd: Descriptive parameters of line distortion.
    
    created by Daniel Haenelt
    Date created: 01-11-2018             
    Last modified: 15-01-2020
    """
    import os
    import numpy as np
    from numpy.linalg import norm
    from scipy.stats import sem
    from nibabel.freesurfer.io import read_geometry, write_morph_data
    from lib.io.read_patch import read_patch
    
    # make output folder
    if not os.path.exists(path_output):
        os.makedirs(path_output)
    
    # load data
    vtx_white, fac_white = read_geometry(file_white)
    _, _, _, ind_patch = read_patch(file_patch) 
    vtx_patch = np.zeros((len(ind_patch),3))  
    vtx_patch[:,0], vtx_patch[:,1], vtx_patch[:,2], _ = read_patch(file_patch)

    # look for faces which exist in the patch
    fac_patch = []
    for i in range(len(fac_white)):
        if np.any(fac_white[i,0]==ind_patch) and np.any(fac_white[i,1]==ind_patch) and np.any(fac_white[i,2]==ind_patch):
            fac_patch.append(fac_white[i,:])
    fac_patch = np.array(fac_patch)

    """
    Areal distortion
    """

    # calculate face-wise areal distortion (before flattening)
    facvtx_white = np.concatenate([vtx_white[fac_patch[:,0]], vtx_white[fac_patch[:,1]], vtx_white[fac_patch[:,2]]], axis=1)
    facvtx0_white = facvtx_white[:,0:6] - np.concatenate([facvtx_white[:,6:9], facvtx_white[:,6:9]], axis=1) # place 3rd vtx at origin
    cp = np.cross(facvtx0_white[:,0:3],facvtx0_white[:,3:6], axisa=1, axisb=1) # cross product
    A_white = norm(cp, axis=1) / 2 # half of the norm
    
    # calculate face-wise areal distortion (after flattening)
    vtx_patch_all = np.zeros_like(vtx_white).astype(float)
    for i in range(len(ind_patch)):
        vtx_patch_all[ind_patch[i],:] = vtx_patch[i,:]
    
    facvtx_patch = np.concatenate([vtx_patch_all[fac_patch[:,0]], vtx_patch_all[fac_patch[:,1]], vtx_patch_all[fac_patch[:,2]]], axis=1)
    facvtx0_patch = facvtx_patch[:,0:6] - np.concatenate([facvtx_patch[:,6:9], facvtx_patch[:,6:9]], axis=1) # place 3rd vtx at origin
    cp = np.cross(facvtx0_patch[:,0:3],facvtx0_patch[:,3:6], axisa=1, axisb=1) # cross product
    A_patch = norm(cp, axis=1) / 2 # half of the norm
    
    # calculate face-wise distortion
    A_dist = A_patch/A_white

    # convert to vertex-wise representation
    VAD = np.zeros(len(vtx_white)).astype(float)
    VAD_miss = 0 
    for i in range(len(ind_patch)):
        temp = np.where(fac_patch==ind_patch[i])
        if np.any(temp[0]):
            VAD[ind_patch[i]] = np.sum(A_dist[temp[0]])/len(temp[0])
        else:
            VAD_miss+=1

    VAD_params = [np.mean(VAD[ind_patch]),
                  np.std(VAD[ind_patch]),
                  sem(VAD[ind_patch]),
                  np.min(VAD[ind_patch]),
                  np.max(VAD[ind_patch]),
                  VAD_miss]
   
    # save morphological data
    write_morph_data(os.path.join(path_output, os.path.basename(file_patch)+".areal_distortion"), VAD)
    
    """
    Linear distortion
    """

    VLD = np.zeros(len(vtx_white)).astype(float)
    VLD_miss = 0
    for i in range(len(ind_patch)):
        node_patch = vtx_patch_all[ind_patch[i]]
        node_white = vtx_white[ind_patch[i]]
        
        ind_temp = fac_patch[np.where(fac_patch==ind_patch[i])[0],:]
        ind_temp = np.reshape(ind_temp,len(ind_temp)*3)
        ind_temp = np.unique(ind_temp)
        ind_temp = np.delete(ind_temp,np.where(ind_temp==ind_patch[i]))
    
        if np.any(ind_temp):
            VLD_patch = 0
            VLD_white = 0
            for j in range(len(ind_temp)):
                VLD_patch += norm(node_patch-vtx_patch_all[ind_temp[j]])
                VLD_white += norm(node_white-vtx_white[ind_temp[j]])
                
            VLD[ind_patch[i]] = VLD_patch/VLD_white
        else:
            VLD_miss+=1

    VLD_params = [np.mean(VLD[ind_patch]),
                  np.std(VLD[ind_patch]),
                  sem(VLD[ind_patch]),
                  np.min(VLD[ind_patch]),
                  np.max(VLD[ind_patch]),
                  VLD_miss]

    # save morphological data
    write_morph_data(os.path.join(path_output, os.path.basename(file_patch)+".line_distortion"), VLD)

    return VAD_params, VLD_params