1 """
2 Functions for mapping from one 2D Cartesian coordinate system to another.
3
4 These are useful for defining projections with nonlinear magnifications,
5 reductions, or other transformations of the input coordinate system.
6
7 Generally, these function objects should work for an arbitrary (x,y) pair,
8 returning new, remapped (x,y), although some classes of mappers may
9 define their range and domain, with undesirable or undefined behavior
10 outside those regions. It is best if such objects raise a suitable
11 exception in those circumstances.
12
13 $Id: basic.py 11296 2010-07-27 14:39:42Z ceball $
14 """
15 __version__='$Revision: 11296 $'
16
17 from math import atan,pi,atan2
18
19 from numpy import exp,log,sqrt,sin,cos,ones,dot
20 from numpy.matlib import matrix
21
22 import param
23
24 from topo.base.functionfamily import CoordinateMapperFn, IdentityMF
25 from topo.misc.util import signabs
26 from topo import numbergen
27
28
29
30
32 """
33 Map all values to the same constant, pre-specified coordinates.
34 """
35
36 x_cons = param.Number(default=0.0,doc="""
37 Constant x value returned by the mapping.""")
38
39 y_cons = param.Number(default=0.0, doc="""
40 Constant y value returned by the mapping.""")
41
45
46
48 """
49 Applies a sequence of coordmappers, left to right.
50 """
51
52 mappers=param.List(default=[],
53 doc="The sequence of mappers to apply.")
54
58
59
60 -class Jitter(CoordinateMapperFn):
72
73
84
85
86 -class Grid(CoordinateMapperFn):
87 """
88 Divides the 2D area into a grid, where all points within each grid
89 element map to the same location.
90 """
91
92 xdensity = param.Number(default=1, bounds=(0,None), doc="""
93 Number of columns per 1.0 input sheet distance horizontally.""")
94
95 ydensity = param.Number(default=1, bounds=(0,None), doc="""
96 Number of rows per 1.0 input sheet distance vertically.""")
97
99 xd=self.xdensity
100 yd=self.ydensity
101
102 xquant=(1.0/xd)*(int(xd*(x+0.5))-(0.5*(xd-1)))
103 yquant=(1.0/yd)*(int(yd*(y+0.5))-(0.5*(yd-1)))
104
105 return xquant,yquant
106
107
109 """
110 Map from polar (radius,angle) to Cartesian (x,y) coordinates.
111 """
112
113 degrees=param.Boolean(default=True,
114 doc="Indicates whether the input angle is in degrees or radians.")
115
117
118 if self.degrees:
119 theta = theta * pi/180
120
121 return r*cos(theta), r*sin(theta)
122
123
125 """
126 Maps from Cartesian (x,y) to polar (radius,angle).
127 """
128
129 degrees = param.Boolean(default=True,
130 doc="Indicates whether the output angle is in degrees or radians.")
131
132 negative_radii = param.Boolean(default = False,
133 doc="""If true, coordinates with negative x values will be
134 given negative radii, and angles between -90 and 90
135 degrees. (useful for mapping to saccade amplitude/direction space)""")
136
151
152
153
154
203
204
206 """
207 Return an affine transformation matrix that translates points by
208 the offset (xoff,yoff).
209 """
210 return matrix([[1, 0, xoff],
211 [0, 1, yoff],
212 [0, 0, 1 ]])
213
214
216 """
217 Return an affine transformation matrix that rotates the points
218 around the origin by t radians.
219 """
220 return matrix([[cos(t), -sin(t), 0],
221 [sin(t), cos(t), 0],
222 [ 0 , 0 , 1]])
223
224
226 """
227 Return an affine translation matrix that scales the points
228 toward/away from the origin by a factor of sx on x-axis and sy on
229 the y-axis.
230 """
231 return matrix([[sx, 0, 0],
232 [ 0, sy, 0],
233 [ 0, 0, 1]])
234
235
246
247
249 """
250 Rotate the input around the origin by an angle in radians.
251 """
252 angle = param.Number(default=0.0)
253
257
258
269
270
272 """
273 Coordinate Mapper that uses an origin-centered 1-D mapping function.
274
275 An abstract mapping function for coordinate mappers that remap
276 based on the radius, x, or y individually. Subclasses should override
277 _map_fn(self,z).
278 """
279 __abstract = True
280
281 in_range = param.Number(default=0.5*sqrt(2),bounds=(0,None),doc="""
282 The maximum range of the mapping input.""")
283 out_range = param.Number(default=0.5*sqrt(2),bounds=(0,None), doc="""
284 The maximum range of the output.""")
285 remap_dimension = param.ObjectSelector(default='radius',
286 objects=['radius','x','y','xy'],doc="""
287 The dimension to remap. ('xy' remaps x and y independently.)""")
288
289
310
312 raise NotImplementedError
313
314
316 """
317 Exponential (magnifying) mapping function.
318
319 Provides a mapping that magnifies the center of the activity image.
320 Parameter k indicates amount of magnification, where 0 means no
321 magnification.
322 """
323
324 k = param.Number(default=1.0,bounds=(0,None))
325
333
334
336 """
337 Provides a mapping that reduces the center of the activity.
338
339 Roughly the inverse of MagnifyingMapper. k indicates amount of reduction.
340 """
341 k = param.Number(default=1.0,bounds=(0,None))
342
347
348
350 """
351 Abstract class for superior colliculus mappings.
352
353 Subclasses of this class implement afferent and efferent mappings
354 from Ottes et al. (1986) Vision Research 26:857-873.
355
356 Default constant values are from Table 1, ibid.
357 """
358 __abstract = True
359
360
361 A = param.Number(default=5.3, doc="""
362 Shape parameter A, in degrees""")
363 Bu = param.Number(default=1.8, doc="""
364 Rostral-caudal scale parameter, in mm""")
365 Bv = param.Number(default=1.8, doc="""
366 Orthogonal (medial-lateral?) scale paraemter in mm/deg""")
367
368 mm_scale = param.Number(default=8.0,doc="""
369 Scale factor to convert constants Bu and Bv from mm to sheet
370 units. Expressed in mm/unit. """)
371
372 amplitude_scale = param.Number(default=1,doc="""
373 Scale factor for saccade command amplitude, expressed in
374 degrees per unit of sheet. Indicates how large a
375 saccade is represented by the x-component of the command
376 input.""")
377
378 direction_scale = param.Number(default=1,doc="""
379 Scale factor for saccade command direction, expressed in
380 degrees per unit of sheet. Indicates what direction of saccade
381 is represented by the y-component of the command input.""")
382
383
385 raise NotImplementedError
386
387
389 """
390 Efferent superior colliculus mapping.
391
392 Provides the output/motor mapping from SC as defined by Ottes et al.
393 (see superclass docs for reference)
394
395 The mapping allows the creation of a single sheet representing
396 both colliculi, one in the x-positive hemisheet and the other in
397 the x-negative hemisheet.
398 """
412
413
414
416 """
417 Afferent superior colliculus mapping.
418
419 Provides the input/retinal mapping to SC as defined by Ottes et al.
420 (see superclass docs for reference)
421
422 The mapping allows the creation of a single sheet representing
423 both colliculi, one in the x-positive hemisheet and the other in
424 the x-negative hemisheet.
425
426 [NOTE: see warning in docs for ottes_inverse_mapping().]
427 """
428
444
445
447 """
448 The efferent mapping function from Ottes et al. (1986)
449 Vision Research 26:857-873.
450
451 Takes saccade with amplitude R (in degrees) and direction
452 phi (in degrees), and returns a location u,v on the colliculus
453 in mm, where the u axis is rostral/caudal, and the v axis is
454 medial/lateral.
455 """
456
457 phi *= pi/180
458 u = Bu * (log(sqrt(R**2 + A**2 + 2*A*R*cos(phi))) - log(A))
459 v = Bv * atan((R*sin(phi))/(R*cos(phi)+A))
460 return u,v
461
462
464 """
465 Approximate inverse of ottes_mapping(), using the inverse function
466 provided in the appendix of Ottes et al. (1986) Vision Research 26:857-873
467
468 Takes takes a location u,v on the colliculus in mm and maps
469 to a retinal eccentricity R and direction phi, in degrees.
470 Inverse is approximate, with increasing error as positions near the
471 edges of the collicular sheet. (I.e. with high absolute v value).
472 """
473
474 rads = pi/180
475 R = A * sqrt(exp(2*u/Bu) - 2*exp(u/Bu)*cos(rads*v/Bv) + 1)
476
477 phi = atan2( (exp(u/Bu)*sin(rads*v/Bv)), (exp(u/Bu)*cos(rads*v/Bv) -1) ) * 180/pi
478
479
480
481 return R,phi*180/pi
482
483
484
486
487 A,Bu,Bv = 5.3,1.8,1.8
488
489 print '%10s %10s | %10s %10s | %10s %10s | %s' \
490 % ('R in','phi in','R out','phi out','R err','phi err','phi ratio')
491 for r in range(10,60,10):
492 for phi in range(-60,60,10):
493 if phi != 0:
494 u,v = ottes_mapping(r,phi,A,Bu,Bv)
495 r2,phi2 = ottes_inverse_mapping(u,v,A,Bu,Bv)
496 print '%10.2f %10.2f | %10.2f %10.2f | %10.2f %10.2f | %.2f' \
497 % (r,phi,r2,phi2,r2-r,phi2-phi,phi/phi2)
498
499
500 __all__ = list(set([k for k,v in locals().items()
501 if isinstance(v,type) \
502 and issubclass(v,CoordinateMapperFn)]))
503