def generate_DROI_details(self, eccentricity_range=None, angles=None, min_variance_explained=0, method=None): ''' generate_DROI_details() yields a set of lazily computed DROI detailed analyses; these analyses are used to generate the DROI table(s). ''' import six from neuropythy.util import curry f = curry( VisualPerformanceFieldsDataset._generate_subject_DROI_details, self.subjects) m = { sid: pimms.lmap({ h: curry(f, sid, h, eccentricity_range=eccentricity_range, angles=angles, min_variance_explained=min_variance_explained, method=method) for h in ['lh', 'rh'] }) for sid in six.iterkeys(self.subjects) } return pimms.persist(m)
def _generate_subject_DROI_details(subjects, sid, h, eccentricity_range=None, angles=None, min_variance_explained=0, method=None): from neuropythy.util import curry import six, pyrsistent as pyr, numpy as np paradigm_order = ['dorsal', 'ventral', 'vertical', 'horizontal'] roi_eccens = VisualPerformanceFieldsDataset.roi_eccens roi_angles = VisualPerformanceFieldsDataset.roi_angles if angles is None or angles is Ellipsis: angles = roi_angles e = eccentricity_range if e is None or e is Ellipsis: e = list(VisualPerformanceFieldsDataset.roi_eccens) if pimms.is_list(e) and all(pimms.is_tuple(q) for q in e): f = VisualPerformanceFieldsDataset._generate_subject_DROI_details res = [f(subjects, sid, h, eccentricity_range=q) for q in e] q = res[0] def _merge(p, a): r = {} for k in six.iterkeys(q[p][a]): u = [u[p][a][k] for u in res if len(u[p][a][k]) > 0] if len(u) == 0: u = np.asarray([], dtype=np.float) else: u = np.concatenate(u) r[k] = u return pyr.pmap(r) return pyr.pmap({ k: pimms.lmap({a: curry(_merge, k, a) for a in angles}) for k in paradigm_order }) f0 = VisualPerformanceFieldsDataset._generate_subject_DROI_data f = lambda sid, h, k: f0(subjects[sid], h, k, eccentricity_range=e, results='all', min_variance_explained=min_variance_explained, method=method) lm0 = pimms.lmap({k: curry(f, sid, h, k) for k in angles}) pfn = lambda p: pimms.lmap( {k: curry(lambda k: lm0[k][p], k) for k in angles}) return pimms.lmap({p: curry(pfn, p) for p in paradigm_order})
def DROI_details(subjects): ''' DROI_details is a nested-dictionary structure of the various DROI details of each subject and hemisphere. ''' import neuropythy as ny, os, six from neuropythy.util import curry f = curry( VisualPerformanceFieldsDataset._generate_subject_DROI_details, subjects) m = { sid: pimms.lmap({h: curry(f, sid, h) for h in ['lh', 'rh']}) for sid in six.iterkeys(subjects) } return pimms.persist(m)
def face_vmag(hemi, retinotopy='any', to=None, **kw): ''' face_vmag(mesh) yields the visual magnification based on the projection of individual faces on the cortical surface into the visual field. face_vmag(mdat) uses the given magnification data mdat (as returned from mag_data()); if valid magnification data is passed then all options related to the mag_data() function are ignored. All options accepted by mag_data() are accepted by disk_vmag(). The additional optional arguments are also accepted: * to (default: None) specifies that the resulting data should be transformed in some way; these transformations are: * None or 'data': returns the full magnification data without transformation; * 'faces': returns a property of the visual magnification value of each face; * 'vertices': returns a property of the visual magnification value of each vertex, as determined by averaging the magnification ''' mdat = mag_data(hemi, retinotopy=retinotopy, **kw) if pimms.is_vector(mdat): return tuple([face_vmag(m, to=to) for m in mdat]) elif pimms.is_map(mdat.keys(), 'int'): return pimms.lazy_map({k: curry(lambda k: face_vmag(mdat[k], to=to), k) for k in six.iterkeys(mdat)}) #TODO: implement the face_vmag calculation using mdat # convert to the appropriate type according to the to param raise NotImplementedError()
def subjects(inferred_maps, boundary_distances, subject_list): ''' subjects is a dictionary of subject objects for all subjects used in the visual performance fields dataset. All subject objects in the subejcts dict include property data on the native hemispheres for inferred retinotopic maps and for V1 boundary distances. ''' from neuropythy.util import curry f = VisualPerformanceFieldsDataset._generate_subject return pimms.lmap({ sid: curry(f, sid, inferred_maps, boundary_distances) for sid in subject_list })
def isoline_vmag(hemi, isolines=None, surface='midgray', min_length=2, **kw): ''' isoline_vmag(hemi) calculates the visual magnification function f using the default set of iso-lines (as returned by neuropythy.vision.visual_isolines()). The hemi argument may alternately be a mesh object. isoline_vmag(hemi, isolns) uses the given iso-lines rather than the default ones. The return value of this funciton is a dictionary whose keys are 'tangential', 'radial', and 'areal', and whose values are the estimated visual magnification functions. These functions are of the form f(x,y) where x and y can be numbers or arrays in the visual field. ''' from neuropythy.util import (curry, zinv) from neuropythy.mri import is_cortex from neuropythy.vision import visual_isolines from neuropythy.geometry import to_mesh # if there's no isolines, get them if isolines is None: isolines = visual_isolines(hemi, **kw) # see if the isolines is a lazy map of visual areas; if so return a lazy map recursing... if pimms.is_vector(isolines.keys(), 'int'): f = lambda k: isoline_vmag(isolines[k], surface=surface, min_length=min_length) return pimms.lazy_map({k:curry(f, k) for k in six.iterkeys(isolines)}) mesh = to_mesh((hemi, surface)) # filter by min length if min_length is not None: isolines = {k: {kk: {kkk: [vvv[ii] for ii in iis] for (kkk,vvv) in six.iteritems(vv)} for (kk,vv) in six.iteritems(v) for iis in [[ii for (ii,u) in enumerate(vv['polar_angles']) if len(u) >= min_length]] if len(iis) > 0} for (k,v) in six.iteritems(isolines)} (rlns,tlns) = [isolines[k] for k in ['eccentricity', 'polar_angle']] if len(rlns) < 2: raise ValueError('fewer than 2 iso-eccentricity lines found') if len(tlns) < 2: raise ValueError('fewer than 2 iso-angle lines found') # grab the visual/surface lines ((rvlns,tvlns),(rslns,tslns)) = [[[u for lns in six.itervalues(xlns) for u in lns[k]] for xlns in (rlns,tlns)] for k in ('visual_coordinates','surface_coordinates')] # calculate some distances (rslen,tslen) = [[np.sqrt(np.sum((sx[:,:-1] - sx[:,1:])**2, 0)) for sx in slns] for slns in (rslns,tslns)] (rvlen,tvlen) = [[np.sqrt(np.sum((vx[:,:-1] - vx[:,1:])**2, 0)) for vx in vlns] for vlns in (rvlns,tvlns)] (rvxy, tvxy) = [[0.5*(vx[:,:-1] + vx[:,1:]) for vx in vlns] for vlns in (rvlns,tvlns)] (rvlen,tvlen,rslen,tslen) = [np.concatenate(u) for u in (rvlen,tvlen,rslen,tslen)] (rvxy,tvxy) = [np.hstack(vxy) for vxy in (rvxy,tvxy)] (rvmag,tvmag) = [vlen * zinv(slen) for (vlen,slen) in zip([rvlen,tvlen],[rslen,tslen])] return {k: {'visual_coordinates':vxy, 'visual_magnification': vmag, 'visual_lengths': vlen, 'surface_lengths': slen} for (k,vxy,vmag,vlen,slen) in zip(['radial','tangential'], [rvxy,tvxy], [rvmag,tvmag], [rvlen,tvlen], [rslen,tslen])}
def disk_vmag(hemi, retinotopy='any', to=None, **kw): ''' disk_vmag(mesh) yields the visual magnification based on the projection of disks on the cortical surface into the visual field. All options accepted by mag_data() are accepted by disk_vmag(). ''' mdat = mag_data(hemi, retinotopy=retinotopy, **kw) if pimms.is_vector(mdat): return tuple([face_vmag(m, to=to) for m in mdat]) elif pimms.is_vector(mdat.keys(), 'int'): return pimms.lazy_map({ k: curry(lambda k: face_vmag(mdat[k], to=to), k) for k in six.iterkeys(mdat) }) #TODO: implement the disk_vmag calculation using mdat # convert to the appropriate type according to the to param raise NotImplementedError()
def boundary_distances(pseudo_path, subject_list, inferred_maps): ''' boundary_distances is a nested-dictionary structure containing distances between each vertex and a V1 boundary. If x is boundar_distances[sid][h][b][k] then x is the distance between the k'th vertex and boundary b ("ventral", "dorsal", or "horizontal") in the h hemisphere ("lh" or "rh") of the subject with ID sid. ''' import os, six from neuropythy.util import curry from neuropythy import load 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}) return pimms.persist({ sid: pimms.lmap( {h: curry(_load_distances, sid, h) for h in ['lh', 'rh']}) for sid in subject_list })
def inferred_maps(pseudo_path, subject_list): ''' inferred_maps is a nested-dictionary structure containing the retinotopic maps inferred by using Bayesian inference on the retinotopic maps of the subjects in the HCP 7T Retinotopy Dataset. ''' import os, six from neuropythy.util import curry from neuropythy import load inffiles = VisualPerformanceFieldsDataset.inferred_map_files def _load_infmaps(sid, h, patt): flnm = pseudo_path.local_path('inferred_maps', patt % (sid, h)) return load(flnm) return pimms.persist({ sid: { h: pimms.lmap({('inf_' + k): curry(_load_infmaps, sid, h, v) for (k, v) in six.iteritems(inffiles)}) for h in ['lh', 'rh'] } for sid in subject_list })
def disk_vmag(hemi, retinotopy='any', to=None, **kw): ''' disk_vmag(mesh) yields the visual magnification based on the projection of disks on the cortical surface into the visual field. All options accepted by mag_data() are accepted by disk_vmag(). ''' mdat = mag_data(hemi, retinotopy=retinotopy, **kw) if pimms.is_vector(mdat): return tuple([face_vmag(m, to=to) for m in mdat]) elif pimms.is_vector(mdat.keys(), 'int'): return pimms.lazy_map({k: curry(lambda k: disk_vmag(mdat[k], to=to), k) for k in six.iterkeys(mdat)}) # for disk cmag we start by making sets of circular points around each vertex msh = mdat['submesh'] n = msh.vertex_count vxy = mdat['visual_coordinates'].T sxy = msh.coordinates.T neis = msh.tess.indexed_neighborhoods nnei = np.asarray(list(map(len, neis))) emax = np.max(nnei) whs = [np.where(nnei > k)[0] for k in range(emax)] neis = np.asarray([u + (-1,)*(emax - len(u)) for u in neis]) dist = np.full((n, emax), np.nan) for (k,wh) in enumerate(whs): nei = neis[wh,k] dxy = sxy[nei] - sxy[wh] dst = np.sqrt(np.sum(dxy**2, axis=1)) dist[wh,k] = dst # find min dist from each vertex ww = np.where(np.max(np.isfinite(dist), axis=1) == 1)[0] whs = [np.intersect1d(wh, ww) for wh in whs] radii = np.full(n, np.nan) radii[ww] = np.nanmin(dist[ww], 1) dfracs = (radii * zinv(dist.T)).T ifracs = 1 - dfracs # make points that distance from each edge ellipses = np.full((n, emax, 2), np.nan) for (k,wh) in enumerate(whs): xy0 = vxy[wh] xyn = vxy[neis[wh,k]] ifr = ifracs[wh,k] dfr = dfracs[wh,k] uxy1 = xy0 * np.transpose([ifr, ifr]) uxy2 = xyn * np.transpose([dfr, dfr]) uxy = (uxy1 + uxy2) ellipses[wh,k,:] = uxy - xy0 # we want to rotate the points to be along their center's implied rad/tan axis vrs = np.sqrt(np.sum(vxy**2, axis=1)) irs = zinv(vrs) coss = vxy[:,0] * irs sins = vxy[:,1] * irs # rotating each ellipse by negative-theta gives us x-radial and y=tangential cels = (coss * ellipses.T) sels = (sins * ellipses.T) rots = np.transpose([cels[0] + sels[1], cels[1] - sels[0]], [1,2,0]) # now we fit the best rad/tan-oriented ellipse we can with the given center rsrt = np.sqrt(np.sum(rots**2, axis=2)).T (csrt,snrt) = zinv(rsrt) * rots.T # ... a*cos(rots) + b*sin(rots) ~= r(rots) where a = radial vmag and b = tangential vmag axes = [] cods = [] idxs = [] for (r,c,s,k,irad,i) in zip(rsrt,csrt,snrt,nnei,zinv(radii),range(len(nnei))): if k < 3: continue (c,s,r) = [u[:k] for u in (c,s,r)] # if the center point is way outside the min/max, skip it (x,y) = (r*c, r*s) if len(np.unique(np.sign(x))) < 2 or len(np.unique(np.sign(y))) < 2: continue mudst = np.sqrt(np.sum(np.mean([x, y], axis=1)**2)) if mudst > np.min(r): continue # okay, fit an ellipse... fs = np.transpose([c,s]) try: (ab,rss,rnk,svs) = np.linalg.lstsq(fs, r, rcond=None) if len(rss) == 0 or rnk < 2 or np.min(svs/np.sum(svs)) < 0.01: continue axes.append(np.abs(ab) * irad) cods.append(1 - rss[0]*zinv(np.sum(r**2))) idxs.append(i) except Exception as e: continue (axes, cods, idxs) = [np.asarray(u) for u in (axes, cods, idxs)] return (idxs, axes, cods)
def mag_data(hemi, retinotopy='any', surface='midgray', mask=None, weights=Ellipsis, weight_min=0, weight_transform=Ellipsis, visual_area=None, visual_area_mask=Ellipsis, eccentricity_range=None, polar_angle_range=None): ''' mag_data(hemi) yields a map of visual/cortical magnification data for the given hemisphere. mag_data(mesh) uses the given mesh. mag_data([arg1, arg2...]) maps over the given hemisphere or mesh arguments. mag_data(subject) is equivalent to mag_data([subject.lh, subject.rh]). mag_data(mdata) for a valid magnification data map mdata (i.e., is_mag_data(mdata) is True or mdata is a lazy map with integer keys) always yields mdata without considering any additional arguments. The data structure returned by magdata is a lazy map containing the keys: * 'surface_coordinates': a (2 x N) or (3 x N) matrix of the mesh coordinates in the mask (usually in mm). * 'visual_coordinates': a (2 x N) matrix of the (x,y) visual field coordinates (in degrees). * 'surface_areas': a length N vector of the surface areas of the faces in the mesh. * 'visual_areas': a length N vector of the areas of the faces in the visual field. * 'mesh': the full mesh from which the surface coordinates are obtained. * 'submesh': the submesh of mesh of just the vertices in the mask (may be identical to mesh). * 'mask': the mask used. * 'retinotopy_data': the full set of retinotopy_data from the hemi/mesh; note that this will include the key 'weights' of the weights actually used and 'visual_area' of the found or specified visual area. * 'masked_data': the subsampled retinotopy data from the hemi/mesh. Note that if a visual_area property is found or provided (see options below), instead of yielding a map of the above, a lazy map whose keys are the visual areas and whose values are the maps described above is yielded instead. The following named options are accepted (in order): * retinotopy ('any') specifies the value passed to the retinotopy_data function to obtain the retinotopic mapping data; this may be a map of such data. * surface ('midgray') specifies the surface to use. * mask (None) specifies the mask to use. * weights, weight_min, weight_transform (Ellipsis, 0, Ellipsis) are used as in the to_property() function in neuropythy.geometry except weights, which, if equal to Ellipsis, attempts to use the weights found by retinotopy_data() if any. * visual_area (Ellipsis) specifies the property to use for the visual area label; Ellipsis is equivalent to whatever visual area label is found by the retinotopy_data() function if any. * visual_area_mask (Ellipsis) specifies which visual areas to include in the returned maps, assuming a visual_area property is found; Ellipsis is equivalent to everything but 0; None is equivalent to everything. * eccentricity_range (None) specifies the eccentricity range to include. * polar_angle_range (None) specifies the polar_angle_range to include. ''' if is_mag_data(hemi): return hemi elif pimms.is_lazy_map(hemi) and pimms.is_vector(hemi.keys(), 'int'): return hemi if mri.is_subject(hemi): hemi = (hemi.lh. hemi.rh) if pimms.is_vector(hemi): return tuple([mag_data(h, retinotopy=retinotopy, surface=surface, mask=mask, weights=weights, weight_min=weight_min, weight_transform=weight_transform, visual_area=visual_area, visual_area_mask=visual_area_mask, eccentricity_range=eccentricity_range, polar_angle_range=polar_angle_range) for h in hemi]) # get the mesh mesh = geo.to_mesh((hemi, surface)) # First, find the retino data retino = retinotopy_data(hemi, retinotopy) # we can process the rest the mask now, including weights and ranges if weights is Ellipsis: weights = retino.get('variance_explained', None) mask = hemi.mask(mask, indices=True) (arng,erng) = (polar_angle_range, eccentricity_range) (ang,ecc) = (retino['polar_angle'], retino['eccentricity']) if pimms.is_str(arng): tmp = to_hemi_str(arng) arng = (-180,0) if tmp == 'rh' else (0,180) if tmp == 'lh' else (-180,180) elif arng is None: tmp = ang[mask] tmp = tmp[np.isfinite(tmp)] arng = (np.min(tmp), np.max(tmp)) if erng is None: tmp = ecc[mask] tmp = tmp[np.isfinite(tmp)] erng = (0, np.max(tmp)) elif pimms.is_scalar(erng): erng = (0, erng) (ang,wgt) = hemi.property(retino['polar_angle'], weights=weights, weight_min=weight_min, weight_transform=weight_transform, yield_weight=True) ecc = hemi.property(retino['eccentricity'], weights=weights, weight_min=weight_min, weight_transform=weight_transform, data_range=erng) # apply angle range if given ((mn,mx),mid) = (arng, np.mean(arng)) oks = mask[np.isfinite(ang[mask])] u = ang[oks] u = np.mod(u + 180 - mid, 360) - 180 + mid ang[oks[np.where((mn <= u) & (u < mx))[0]]] = np.inf # mark/unify the out-of-range ones bad = np.where(np.isinf(ang) | np.isinf(ecc))[0] ang[bad] = np.inf ecc[bad] = np.inf wgt[bad] = 0 wgt *= zinv(np.sum(wgt[mask])) # get visual and surface coords vcoords = np.asarray(as_retinotopy(retino, 'geographical')) scoords = mesh.coordinates # now figure out the visual area so we can call down if we need to if visual_area is Ellipsis: visual_area = retino.get('visual_area', None) if visual_area is not None: retino['visual_area'] = visual_area if wgt is not None: retino['weights'] = wgt rdata = pimms.persist(retino) # calculate the range area (tmn,tmx) = [np.pi/180.0 * u for u in arng] if tmx - tmn >= 2*np.pi: (tmn,tmx) = (-np.pi,np.pi) (emn,emx) = erng rarea = 0.5 * (emx*emx - emn*emn) * (tmx - tmn) # okay, we have the data organized; we can do the calculation based on this, but we may have a # visual area mask to apply as well; here's how we do it regardless of mask def finish_mag_data(mask): if len(mask) == 0: return None # now that we have the mask, we can subsample submesh = mesh.submesh(mask) mask = mesh.tess.index(submesh.labels) mdata = pyr.pmap({k:(v[mask] if pimms.is_vector(v) else v[:,mask] if pimms.is_matrix(v) else None) for (k,v) in six.iteritems(rdata)}) fs = submesh.tess.indexed_faces (vx, sx) = [x[:,mask] for x in (vcoords, scoords)] (vfx,sfx) = [np.asarray([x[:,f] for f in fs]) for x in (vx, sx)] (va, sa) = [geo.triangle_area(*x) for x in (vfx, sfx)] return pyr.m(surface_coordinates=sx, visual_coordinates=vx, surface_areas=sa, visual_areas=va, mesh=mesh, submesh=submesh, retinotopy_data=rdata, masked_data=mdata, mask=mask, area_of_range=rarea) # if there's no visal area, we just use the mask as is if visual_area is None: return finish_mag_data(mask) # otherwise, we return a lazy map of the visual area mask values visual_area = hemi.property(visual_area, mask=mask, null=0, dtype=np.int) vam = (np.unique(visual_area) if visual_area_mask is None else np.setdiff1d(np.unique(visual_area), [0]) if visual_area_mask is Ellipsis else np.unique(list(visual_area_mask))) return pimms.lazy_map({va: curry(finish_mag_data, mask[visual_area[mask] == va]) for va in vam})
def rtmag_potential(hemi, retinotopy=Ellipsis, mask=Ellipsis, weight=Ellipsis, surface='midgray', min_weight=Ellipsis, min_eccentricity=0.75, visual_area=None, map_visual_areas=Ellipsis, visual_area_field_signs=Ellipsis, measurement_uncertainty=0.4, measurement_knob=1, magnification_knob=2, fieldsign_knob=8, edge_knob=0, rt_knob=0): from neuropythy.vision.retinotopy import clean_retinotopy_potential import neuropythy.optimize as op f_ret = clean_retinotopy_potential(hemi, retinotopy=retinotopy, mask=mask, weight=weight, surface=surface, min_weight=min_weight, min_eccentricity=min_eccentricity, visual_area=visual_area, map_visual_areas=map_visual_areas, visual_area_field_signs=visual_area_field_signs, measurement_uncertainty=measurement_uncertainty, measurement_knob=measurement_knob, magnification_knob=magnification_knob, fieldsign_knob=fieldsign_knob, edge_knob=edge_knob) # process a few additional arguments: if visual_area_field_signs is None: visual_area_field_signs = {} elif visual_area_field_signs is Ellipsis: visual_area_field_signs = {1:-1, 2:1, 3:-1, 4:1} # this may be a lazy map of visual areas; we want to operate on all of them lazily, so wrap the # rest of this model up in a function: def make_potential(va): global_field_sign = None if va is None else visual_area_field_signs.get(va) f_r = f_ret if va is None else f_ret[va] # The initial parameter vector is stored in the meta-data: X0 = f_r.meta_data['X0'] # A few other handy pieces of data we can extract: fieldsign = visual_area_field_signs.get(va) submesh = f_r.meta_data['mesh'] sxyz = submesh.coordinates n = submesh.vertex_count (u,v) = submesh.tess.indexed_edges selen = submesh.edge_lengths sarea = submesh.face_areas m = submesh.tess.edge_count fs = submesh.tess.indexed_faces neis = submesh.tess.indexed_neighborhoods fangs = submesh.face_angles # we're adding r and t (radial and tangential visual magnification) pseudo-parameters to # each vertex; r and t are derived from the position of other vertices; our first step is # to derive these values; for this we start with the parameters themselves: (x,y) = [op.identity[np.arange(k, 2*n, 2)] for k in (0,1)] # okay, we need to setup a bunch of least-squares solutions, one for each vertex: nneis = np.asarray([len(nn) for nn in neis]) maxneis = np.max(nneis) thts = op.atan2(y, x) eccs = op.compose(op.piecewise(op.identity, ((-1e-9, 1e-9), 1)), op.sqrt(x**2 + y**2)) coss = x/eccs sins = y/eccs # organize neighbors: # neis becomes a list of rows of 1st neighbor, second neighbor etc. with -1 indicating none neis = np.transpose([nei + (-1,)*(maxneis - len(nei)) for nei in neis]) qnei = (neis > -1) # mark where there are actually neighbors neis[~qnei] = 0 # we want the -1s (now 0s) to behave okay when passed to a potential index # okay, walk through the neighbors setting up the least squares (r, t) = (None, None) for (k,q,nei) in zip(range(len(neis)), qnei.astype('float'), neis): xx = x[nei] - x yy = y[nei] - y sd = np.sum((sxyz[:,nei].T - sxyz[:,k])**2, axis=1) (xx, yy) = (xx*coss + yy*sins, yy*coss - xx*sins) xterm = (op.abs(xx) * q) yterm = (op.abs(yy) * q) r = xterm if r is None else (r + xterm) t = yterm if t is None else (t + yterm) (r, t) = [uu * zinv(nneis) for uu in (r, t)] # for neighboring edges, we want r and t to be similar to each other f_rtsmooth = op.sum((r[v]-r[u])**2 + (t[v]-t[u])**2) / m # we also want r and t to predict the radial and tangential magnification of the node, so # we want to make sure that edges are the right distances away from each other based on the # surface edge lengths and the distance around the vertex at the center # for this we'll want some constant info about the surface edges/angles # okay, in terms of the visual field coordinates of the parameters, we will want to know # the angular position of each node # organize face info mnden = 0.0001 (e,qs,qt) = np.transpose([(i,e[0],e[1]) for (i,e) in enumerate(submesh.tess.edge_faces) if len(e) == 2 and selen[i] > mnden if sarea[e[0]] > mnden and sarea[e[1]] > mnden]) (fis,q) = np.unique(np.concatenate([qs,qt]), return_inverse=True) (qs,qt) = np.reshape(q, (2,-1)) o = len(fis) faces = fs[:,fis] fangs = fangs[:,fis] varea = op.signed_face_areas(faces) srfangmtx = sps.csr_matrix( (fangs.flatten(), (faces.flatten(), np.concatenate([np.arange(o), np.arange(o), np.arange(o)]))), (n, o)) srfangtot = flattest(srfangmtx.sum(axis=1)) # normalize this angle matrix by the total and put it back in the same order as faces srfangmtx = zdivide(srfangmtx, srfangtot / (np.pi*2)).tocsr().T nrmsrfang = np.array([sps.find(srfangmtx[k])[2][np.argsort(fs[:,k])] for k in range(o)]).T # okay, now compare these to the actual angles; # we also want to know, for each edge, the angle relative to the radial axis; let's start # by organizing the faces into the units we compute over: (fa,fb,fc) = [np.concatenate([faces[k], faces[(k+1)%3], faces[(k+2)%3]]) for k in range(3)] atht = thts[fa] # we only have to worry about the (a,b) and (a,c) edges now; from the perspective of a... bphi = op.atan2(y[fb] - y[fa], x[fb] - x[fa]) - atht cphi = op.atan2(y[fc] - y[fa], x[fc] - x[fa]) - atht ((bcos,bsin),(ccos,csin)) = bccssn = [(op.cos(q),op.sin(q)) for q in (bphi,cphi)] # the distance should be predicted by surface edge length times ellipse-magnification # prediction; we have made uphi and vphi so that radial axis is x axis and tan axis is y (ra,ta) = (op.abs(r[fa]), op.abs(t[fa])) bslen = np.sqrt(np.sum((sxyz[:,fb] - sxyz[:,fa])**2, axis=0)) cslen = np.sqrt(np.sum((sxyz[:,fc] - sxyz[:,fa])**2, axis=0)) bpre_x = bcos * ra * bslen bpre_y = bsin * ta * bslen cpre_x = ccos * ra * cslen cpre_y = csin * ta * cslen # if there's a global field sign, we want to invert these predictions when the measured # angle is the wrong sign if global_field_sign is not None: varea_f = varea[np.concatenate([np.arange(o) for _ in range(3)])] * global_field_sign fspos = (op.sign(varea_f) + 1)/2 fsneg = 1 - fspos (bpre_x,bpre_y,cpre_x,cpre_y) = ( bpre_x*fspos - cpre_x*fsneg, bpre_y*fspos - cpre_y*fsneg, cpre_x*fspos - bpre_x*fsneg, cpre_y*fspos - bpre_y*fsneg) (ax,ay,bx,by,cx,cy) = [x[fa],y[fa],x[fb],y[fb],x[fc],y[fc]] (cost,sint) = [op.cos(atht), op.sin(atht)] (bpre_x, bpre_y) = (bpre_x*cost - bpre_y*sint + ax, bpre_x*sint + bpre_y*cost + ay) (cpre_x, cpre_y) = (cpre_x*cost - cpre_y*sint + ax, cpre_x*sint + cpre_y*cost + ay) # okay, we can compare the positions now... f_rt = op.sum((bpre_x-bx)**2 + (bpre_y-by)**2 + (cpre_x-cx)**2 + (cpre_y-cy)**2) * 0.5/o f_vmag = f_rtsmooth # + f_rt #TODO: the rt part of this needs to be debugged wgt = 0 if rt_knob is None else 2.0**rt_knob f = f_r if rt_knob is None else (f_r + f_vmag) if rt_knob == 0 else (f_r + w*f_vmag) md = pimms.merge(f_r.meta_data, dict(f_retinotopy=f_r, f_vmag=f_vmag, f_rtsmooth=f_rtsmooth, f_rt=f_rt)) object.__setattr__(f, 'meta_data', md) return f if pimms.is_map(f_ret): return pimms.lazy_map({va: curry(make_potential, va) for va in six.iterkeys(f_ret)}) else: return make_potential(None)