def DROI_table(pseudo_path): ''' DROI_table (distance-based ROI table) is a dataframe summarizing all the data from all the hemispheres and all the distance-based wedge ROIs used in the visual performance fields project. ''' import neuropythy as ny df = ny.load(pseudo_path.local_path('DROI_table.csv')) df.set_index(['sid', 'hemisphere']) return df
def generate_DROI_tables(self, nprocs=None, printstatus=False, angles=None, eccens=None, min_variance_explained=0, method=None, tempdir=None): ''' generate_DROI_tables() recalculates the set of distance-based ROIs for each subject based on the data in the inferred maps and pRFs. ''' import neuropythy as ny, os, six, pyrsistent as pyr, multiprocessing as mp, numpy as np drois = {} subject_list = self.subject_list nsubs = len(subject_list) f = VisualPerformanceFieldsDataset._generate_DROI_tables_call if nprocs is None or nprocs is Ellipsis: nprocs = mp.cpu_count() if nprocs < 2: nprocs = 1 if nprocs > 1: if tempdir is None: tdir = ny.util.tmpdir() else: tdir = tempdir for ii in np.arange(0, nsubs, nprocs): mx = np.min([len(subject_list), ii + nprocs]) if printstatus: print("%2d - %3d%%" % ((ii + 1) / nsubs * 100, mx / nsubs * 100)) pool = mp.Pool(nprocs) sids = subject_list[ii:ii + nprocs] tbls = pool.map(f, [ (sid, angles, eccens, tdir, min_variance_explained, method) for sid in sids ]) pool.close() # add these data into the overall roi table for (sid, tbl) in zip(sids, tbls): drois[sid] = ny.load(tbl) else: for (ii, sid) in enumerate(subject_list): if printstatus and ii % 10 == 9: print('%3d (%d), %4.1f%%' % (ii, sid, ii / len(subject_list) * 100.0)) drois[sid] = f((sid, angles, eccens, tdir, min_variance_explained, method)) return pyr.pmap(drois)
tt = {} # Load the params first: try: with open(os.path.join(tdir, 'params.json'), 'r') as fl: params = json.load(fl) except Exception: warn('Could not load params.json file in dir: %s' % (tdir, )) continue # Then get the stimfile: stimfile = params.get('stimulus_file', 'stimulus.nii.gz') if not os.path.isabs(stimfile): stimfile = os.path.join(tdir, stimfile) if not os.path.isfile(stimfile): warn('Could not find designated stimfile (%s) in test dir %s!' % (stimfile, tdir)) continue stim = ny.load(stimfile, to='image') # Then the data: datafile = params.get('data_file', 'data.nii.gz') if not os.path.isabs(datafile): datafile = os.path.join(tdir, datafile) if not os.path.isfile(datafile): warn('Could not find designated datafile (%s) in test dir %s!' % (datafile, tdir)) continue data = ny.load(datafile, to='image') # Okay, make sure there is an indication of the screen width: if 'pixels_per_degree' not in params: if 'screen_width' in params and 'screen_distance' in params: sw = params['screen_width'] sd = params['screen_distance'] d2p = 180 / np.pi * 2 * np.arctan2(sw / 2, sd) else:
# Additional matplotlib preferences: font_data = {'family':'sans-serif', 'sans-serif':['Helvetica Neue', 'Helvetica', 'Arial'], 'size': 10, 'weight': 'light'} mpl.rc('font',**font_data) # we want relatively high-res images, especially when saving to disk. mpl.rcParams['figure.dpi'] = 72*2 mpl.rcParams['savefig.dpi'] = 72*4 # %% markdown # #Load Data # % # This is for the original 4x4 nifti nii1 = ny.load('~/toolboxes/PRFmodel/data/examples/synthDataExample2_TR2.nii.gz', to='image') data = np.reshape(nii1.dataobj, [nii1.shape[0]*nii1.shape[1], nii1.shape[-1]]) # This is for the 1D nifti # With the 1D nifti I thing we will save lots of orientation problems. For example, # Freesurfers MRIread read the data in different order than niftiRead, flipping x and y. # With a 1D, we do a squeeze and that's it. # nii1 = ny.load('~/toolboxes/PRFmodel/data/examples/synthDataExample3_1D_TR2.nii.gz', to='image') # data = np.squeeze(nii1.dataobj) input_data = data[4,:] # Noah said he used the 5th voxel for the example # Stimulus related (we passed it as a nifti, to avoid sending matlab specific things) nii2 = ny.load('~/toolboxes/PRFmodel/data/examples/Exp-103_binary-true_size-20x20.nii.gz', to='image') stim = np.squeeze(nii2.dataobj) # stim = np.roll(stim, -5, axis=2) stimulus = popeye.visual_stimulus.VisualStimulus(stim.astype('int16'), 30, 10.57962, 0.5, 2, 'int16')
def main(args): # Parse the arguments... parser = argparse.ArgumentParser() parser.add_argument( 'reg', metavar='registration_file', nargs=1, help=('The distort2anat_tkreg.dat or similar file: the registration' ' file, in FreeSurfer\'s tkreg format, to apply to the EPIs.')) parser.add_argument( 'epis', metavar='EPI', type=str, nargs='+', help='The EPI files to be converted to anatomical orientation') parser.add_argument( '-t', '--tag', required=False, default='-', dest='tag', nargs=1, help=('A tag to append to the output filenames; if given as - or' ' omitted, overwrites original files.')) parser.add_argument( '-s', '--surf', required=False, default=False, dest='surface', action='store_true', help=('If provided, instructs the script to also produce files of the ' 'time-series resampled on the cortical surface.')) parser.add_argument( '-o', '--out', required=False, default='.', dest='outdir', help=( 'The output directory to which the files should be written; by' ' default this is the current directory (.); note that if this' ' directory also contains the EPI files and there is no tag given,' ' then the EPIs will be overwritten.')) parser.add_argument( '-m', '--method', required=False, default='linear', dest='method', help=('The method to use for volume-to-surface interpolation; this may' ' be nearest or linear; the default is linear.')) parser.add_argument( '-l', '--layer', required=False, default='midgray', dest='layer', help=( 'Specifies the cortical layer to user in interpolation from volume' ' to surface. By default, uses midgray. May be set to a value' ' between 0 (white) and 1 (pial) to specify an intermediate surface' ' or may be simply white, pial, or midgray.')) parser.add_argument( '-d', '--subjects-dir', required=False, default=None, dest='sdir', help=('Specifies the subjects directory to use; by default uses the' ' environment variable SUBJECTS_DIR.')) parser.add_argument('-v', '--verbose', required=False, default=False, action='store_true', dest='verbose', help='Print verbose output') if args[0].startswith('python'): args = args[2:] else: args = args[1:] args = parser.parse_args(args) # Check some of the arguments... epis = args.epis if len(epis) < 1: raise RuntimeError('No EPIs given') tag = args.tag[0] if tag == '-': tag = '' dosurf = args.surface outdir = args.outdir if not os.path.isdir(outdir): raise RuntimeError('Directory %s does not exist' % outdir) if args.verbose: def note(*args): six.print_(*args, flush=True) return True else: def note(*args): return False try: args.layer = float(args.layer) except: pass # Read in the registration file args.reg = args.reg[0] if not os.path.isfile(args.reg): raise RuntimeError('Given registration file not found: %s' % args.reg) with open(args.reg, 'r') as f: lines = [] while True: s = f.readline() if s is None or s == '': break lines.append(s) # This tells us some info... sub = lines[0].strip() if args.sdir is not None: ny.add_subject_path(args.sdir) try: sub = ny.freesurfer_subject(sub) except: raise ValueError( 'No subject %s; you may need to set your SUBJECTS_DIR' % sub) affine = np.asarray([[float(ss) for ss in s.split()] for s in lines[4:8]]) affinv = np.linalg.inv(affine) displm = sub.lh.affine # loop over the given EPIs for epi in epis: note('Processing EPI %s...' % epi) # import the epi file.. img = ny.load(epi, to='image') # edit the header... note(' - Correcting volume orientation...') new_affine = np.dot(displm, np.dot(affinv, ny.freesurfer.tkr_vox2ras(img))) newimg = nib.Nifti1Image(img.dataobj, new_affine, img.header) (epi_dir, epi_flnm) = os.path.split(epi) if epi_flnm[:-4] in ['.mgz', '.mgh', '.nii']: pre = epi_flnm[:-4] suf = epi_flnm[-4:] else: pre = epi_flnm[:-7] suf = epi_flnm[-7:] srf_flnm = pre + tag + '.mgz' epi_flnm = pre + tag + suf newimg.to_filename(os.path.join(args.outdir, epi_flnm)) # okay, now project to the surface if args.surface: note(' - Projecting to surface...') (ldat, rdat) = sub.image_to_cortex(newimg, surface=args.layer, method=args.method, dtype=np.float32) # we need to fix the dimensions... for (d, h) in zip([ldat, rdat], ['lh', 'rh']): if d.shape[-1] == 1: # then this should properly be a 3d MGH image, not a 4d one. im = nib.freesurfer.mghformat.MGHImage( np.transpose(reduce(np.expand_dims, [-1], d), (0, 2, 1)), np.eye(4)) else: im = nib.freesurfer.mghformat.MGHImage( np.transpose(reduce(np.expand_dims, [-1, -1], d), (0, 2, 3, 1)), np.eye(4)) im.to_filename(os.path.join(args.outdir, h + '.' + srf_flnm)) # That's it! return 0
def import_benson14_from_freesurfer(freesurfer_subject, max_eccentricity, modality='surface', import_filter=None): ''' import_benson14_from_freesurfer is a calculation that imports (or creates then imports) the Benson et al. (2014) template of retinotopy for the subject, whose neuropythy.freesurfer Subject object must be provided in the parameter freesurfer_subject. The optional parameter modality (default: 'volume') may be either 'volume' or 'surface', and determines if the loaded modality is volumetric or surface-based. Required afferent parameters: @ freesurfer_subject Must be a valid neuropythy.freesurfer.Subject object. Optional afferent parameters: @ modality May be 'volume' or 'surface' to specify the anatomical modality. @ max_eccentricity May specifies the maximum eccentricity value to use. @ import_filter If specified, may give a function that accepts four parameters: f(polar_angle, eccentricity, label, hemi); if this function fails to return True for the appropriate values of a particular vertex/voxel, then that vertex/voxel is not included in the prediction. Provided efferent values: @ polar_angles Polar angle values for each vertex/voxel. @ eccentricities Eccentricity values for each vertex/voxel. @ labels An integer label 1, 2, or 3 for V1, V2, or V3, one per vertex/voxel. @ hemispheres 1 if left, -1 if right for all vertex/voxel. @ cortex_indices For vertices, the vertex index (in the appropriate hemisphere) for each; for voxels, the (i,j,k) voxel index for each. @ cortex_coordinates For voxels, this is the (i,j,k) voxel index (same as cortex_indices); for surfaces, this is ths (x,y,z) position of each vertex in surface-space. Notes: * polar_angles are always given such that a negative polar angle indicates a RH value and a positive polar angle inidicates a LH value * cortex_indices is different for surface and volume modalities * labels will always be 1, 2, or 3 indicating V1, V2, or V3 ''' max_eccentricity = max_eccentricity.to(units.deg) if pimms.is_quantity(max_eccentricity) else \ max_eccentricity*units.deg subject = freesurfer_subject if modality.lower() == 'volume': # make sure there are template volume files that match this subject ang = os.path.join(subject.path, 'mri', 'benson14_angle.mgz') ecc = os.path.join(subject.path, 'mri', 'benson14_eccen.mgz') lab = os.path.join(subject.path, 'mri', 'benson14_varea.mgz') if not os.path.exists(ang) or not os.path.exists( ecc) or not os.path.exists(lab): # Apply the template first... neurocmd.benson14_retinotopy.main(subject.path) if not os.path.exists(ang) or not os.path.exists( ecc) or not os.path.exists(lab): raise ValueError('No areas template found/created for subject: ' + lab) angle_mgz = fs.mghformat.load(ang) eccen_mgz = fs.mghformat.load(ecc) label_mgz = fs.mghformat.load(lab) ribbon_mgzs = (subject.mgh_images['lh.ribbon'], subject.mgh_images['rh.ribbon']) # The variables are all mgz volumes, so we need to extract the values: labels = np.round(np.abs(label_mgz.dataobj.get_unscaled())) angles = angle_mgz.dataobj.get_unscaled() eccens = eccen_mgz.dataobj.get_unscaled() (lrib, rrib) = [r.dataobj.get_unscaled() for r in ribbon_mgzs] # Find the voxel indices first: # for now we only look at v1-v3 labels[labels > 3] = 0 coords = np.asarray(np.where(labels.astype(bool))).T # Grab the hemispheres; filter down if something isn't in the ribbon tmp = [(1 if lrib[i, j, k] == 1 else -1, (i, j, k)) for (i, j, k) in coords if lrib[i, j, k] != 0 or rrib[i, j, k] != 0 if eccens[i, j, k] < max_eccentricity.m] hemis = np.asarray([r[0] for r in tmp], dtype=np.int) idcs = np.asarray([r[1] for r in tmp], dtype=np.int) coords = np.asarray(idcs, dtype=np.float) # Pull out the angle/eccen data angs0 = np.asarray([angles[i, j, k] for (i, j, k) in idcs]) angles = angs0 * hemis eccens = np.asarray([eccens[i, j, k] for (i, j, k) in idcs], dtype=np.float) labels = np.asarray([labels[i, j, k] for (i, j, k) in idcs], dtype=np.int) elif modality.lower() == 'surface': rx = freesurfer_subject.RH.midgray_surface.coordinates.T lx = freesurfer_subject.LH.midgray_surface.coordinates.T # make sure there are template volume files that match this subject lang = os.path.join(subject.path, 'surf', 'lh.benson14_angle.mgz') lecc = os.path.join(subject.path, 'surf', 'lh.benson14_eccen.mgz') llab = os.path.join(subject.path, 'surf', 'lh.benson14_varea.mgz') rang = os.path.join(subject.path, 'surf', 'rh.benson14_angle.mgz') recc = os.path.join(subject.path, 'surf', 'rh.benson14_eccen.mgz') rlab = os.path.join(subject.path, 'surf', 'rh.benson14_varea.mgz') (lang, lecc, llab, rang, recc, rlab) = [ flnm if os.path.isfile(flnm) else flnm[:-4] for flnm in (lang, lecc, llab, rang, recc, rlab) ] if not os.path.exists(lang) or not os.path.exists(rang) or \ not os.path.exists(lecc) or not os.path.exists(recc) or \ not os.path.exists(llab) or not os.path.exists(rlab): # Apply the template first... neurocmd.benson14_retinotopy.main(subject.path) if not os.path.exists(lang) or not os.path.exists(rang) or \ not os.path.exists(lecc) or not os.path.exists(recc) or \ not os.path.exists(llab) or not os.path.exists(rlab): raise ValueError( 'No anatomical template found/created for subject') (lang, lecc, llab, rang, recc, rlab) = [ neuro.load(fl) for fl in (lang, lecc, llab, rang, recc, rlab) ] llab = np.round(np.abs(llab)) rlab = np.round(np.abs(rlab)) (angs0, eccs, labs) = [ np.concatenate([ldat, rdat], axis=0) for (ldat, rdat) in zip([lang, lecc, llab], [rang, recc, rlab]) ] idcs = np.concatenate([range(len(lang)), range(len(rang))], axis=0) valid = np.intersect1d( np.intersect1d(np.where(labs > 0)[0], np.where(labs < 4)[0]), np.where(eccs < max_eccentricity.m)[0]) idcs = idcs[valid] coords = np.concatenate([lx, rx], axis=0)[valid] hemis = np.concatenate([[1 for a in lang], [-1 for a in rang]], axis=0)[valid] # old versions of the template had positive numbers in both hemispheres... if np.mean(angs0[valid[hemis == -1]]) > 0: angles = angs0[valid] * hemis else: angles = angs0[valid] eccens = eccs[valid] labels = np.asarray(labs[valid], dtype=np.int) else: raise ValueError('Option modality must be \'surface\' or \'volume\'') # do the filtering and convert to pvectors if import_filter is None: res = { 'polar_angles': units.degree * angles, 'eccentricities': units.degree * eccens, 'labels': labels, 'cortex_indices': idcs, 'cortex_coordinates': coords, 'hemispheres': hemis } else: sels = [ i for (i, (p, e, l, h)) in enumerate(zip(angles, eccens, labels, hemis)) if import_filter(p, e, l, h) ] res = { 'polar_angles': units.degree * angles[sels], 'eccentricities': units.degree * eccens[sels], 'labels': labels[sels], 'cortex_indices': idcs[sels], 'cortex_coordinates': coords[sels], 'hemispheres': hemis[sels] } # make sure they're all write-only for v in res.itervalues(): v.setflags(write=False) return res
def _load_DROI(sid): # get a subject-specific cache_path cpath = pseudo_path.local_path('DROIs', '%s.csv' % (sid, )) return ny.load(cpath)
def _load_distances(sid, h): flnm = pseudo_path.local_path('distances', '%s_%s.mgz' % (sid, h)) (v, d, h) = load(flnm).T return pimms.persist({'ventral': v, 'dorsal': d, 'horizontal': h})
def _load_infmaps(sid, h, patt): flnm = pseudo_path.local_path('inferred_maps', patt % (sid, h)) return load(flnm)
def main(args): # Parse the arguments... parser = argparse.ArgumentParser() parser.add_argument('sub', metavar='subject', nargs=1, help=('The FreeSurfer subject ID or directory.')) parser.add_argument('outdir', metavar='directory', type=str, nargs=1, help='The directory containing the output nifti files.') parser.add_argument('-x', '--no-surf', required=False, default=True, dest='surface', action='store_false', help=('If provided, instructs the script not to produce files of the ' 'pRF parameters resampled onto the cortical surface.')) parser.add_argument('-m', '--method', required=False, default='linear', dest='method', help=('The method to use for volume-to-surface interpolation; this may' ' be nearest or linear; the default is linear.')) parser.add_argument('-l', '--layer', required=False, default='midgray', dest='layer', help=('Specifies the cortical layer to user in interpolation from volume' ' to surface. By default, uses midgray. May be set to a value' ' between 0 (white) and 1 (pial) to specify an intermediate surface' ' or may be simply white, pial, or midgray.')) parser.add_argument('-d', '--subjects-dir', required=False, default=None, dest='sdir', help=('Specifies the subjects directory to use; by default uses the' ' environment variable SUBJECTS_DIR.')) parser.add_argument('-y', '--no-invert-y', required=False, default=True, dest='invert_y', action='store_false', help=('If provided, does not invert the y-coordinate exported from' ' VistaSoft; by default this inversion is performed.')) parser.add_argument('-v', '--verbose', required=False, default=False, action='store_true', dest='verbose', help='Print verbose output') if args[0].startswith('python'): args = args[2:] else: args = args[1:] args = parser.parse_args(args) # Check some of the arguments... sub = ny.freesurfer_subject(args.sub[0]) dosurf = args.surface outdir = args.outdir[0] if not os.path.isdir(outdir): raise RuntimeError('Directory %s does not exist' % outdir) else: os.chdir(outdir) if args.verbose: def note(*args): six.print_(*args, flush=True) return True else: def note(*args): return False try: args.layer = float(args.layer) except: pass # figure out what datasets are here... dsets = [fl[:-13] for fl in os.listdir('.') if fl.endswith('-xcrds.nii.gz')] # loop over the given EPIs for ds in dsets: note('Processing Dataset %s...' % ds) # import the files... note(' - Importing parameters...') (x,y,s,v) = [ny.load('%s-%s.nii.gz' % (ds, suff), to='image') for suff in ['xcrds','ycrds','sigma','vexpl']] # fix polar angle/eccen note(' - Creating polar angle/eccentricity images...') ang = np.arctan2(-y.get_data() if args.invert_y else y.get_data(), x.get_data()) ang = np.mod((90.0 - 180.0/np.pi * ang) + 180, 360) - 180 a = nib.Nifti1Image(ang, x.affine, x.header) e = nib.Nifti1Image(np.sqrt(y.get_data()**2 + x.get_data()**2), x.affine, x.header) a.to_filename('%s-angle.nii.gz' % ds) e.to_filename('%s-eccen.nii.gz' % ds) # surfaces... if not dosurf: continue note(' - Projecting to surface...') to_output = {'xcrds':x, 'ycrds':y, 'sigma': s, 'vexpl': v} xy = ([0,0],[0,0]) for (dname,im) in six.iteritems(to_output): (ldat, rdat) = sub.image_to_cortex(im, args.layer, method=args.method, dtype=np.float32, weights=v) if dname == 'xcrds': xy[0][0] = ldat xy[1][0] = rdat elif dname == 'ycrds': xy[0][1] = ldat xy[1][1] = rdat # we need to fix the dimensions... for (d,h) in zip([ldat,rdat], ['lh','rh']): ny.save('%s.%s-%s.mgz' % (h, ds, dname), d) # Special handling for the angle and eccen (avoids need for circular averaging) xy = (np.asarray(xy[0]), np.asarray(xy[1])) (lecc,recc) = [np.sqrt(np.sum(u**2, axis=0)) for u in xy] (lang,rang) = [np.arctan2(-u[1] if args.invert_y else u[1], u[0]) for u in (xy[0] * ny.util.zinv(lecc), xy[1] * ny.util.zinv(recc))] (lang,rang) = [np.mod((90 - 180/np.pi * aa) + 180, 360) - 180 for aa in (lang,rang)] # export these... for (h,nm,dat) in [('lh','eccen',lecc), ('lh','angle',lang), ('rh','eccen',recc), ('rh','angle',rang)]: ny.save('%s.%s-%s.mgz' % (h, ds, nm), dat) # That's it! return 0