1 from __future__ import with_statement
2 """
3 Simple two-dimensional mathematical or geometrical pattern generators.
4
5 $Id: basic.py 11327 2010-07-31 10:43:18Z ceball $
6 """
7 __version__='$Revision: 11327 $'
8
9 from math import pi, sqrt
10
11 import numpy
12 from numpy.oldnumeric import around,bitwise_and,sin,cos,bitwise_or
13 from numpy import asarray, float32, nonzero, zeros, shape, hstack,\
14 linspace, abs, round, fft, alltrue
15
16 import param
17 from param.parameterized import ParamOverrides,as_uninitialized
18
19 import topo
20
21 from topo.base.patterngenerator import Constant, PatternGenerator
22
23 from topo.base.arrayutil import wrap
24 from topo.base.sheetcoords import SheetCoordinateSystem
25 from topo.misc.patternfn import gaussian,exponential,gabor,line,disk,ring,\
26 sigmoid,arc_by_radian,arc_by_center,smooth_rectangle,float_error_ignore
27
28 from topo import numbergen
29
30
31
32
33
34
35
36
37 -class Null(Constant):
42
45 """
46 Constant pattern on in half of the plane, and off in the rest,
47 with optional Gaussian smoothing.
48 """
49
50 smoothing = param.Number(default=0.02,bounds=(0.0,None),softbounds=(0.0,0.5),
51 precedence=0.61,doc="Width of the Gaussian fall-off.")
52
62
65 """
66 2D Gaussian pattern generator.
67
68 The sigmas of the Gaussian are calculated from the size and
69 aspect_ratio parameters:
70
71 ysigma=size/2
72 xsigma=ysigma*aspect_ratio
73
74 The Gaussian is then computed for the given (x,y) values as::
75
76 exp(-x^2/(2*xsigma^2) - y^2/(2*ysigma^2)
77 """
78
79 aspect_ratio = param.Number(default=1/0.31,bounds=(0.0,None),softbounds=(0.0,6.0),
80 precedence=0.31,doc="""
81 Ratio of the width to the height.
82 Specifically, xsigma=ysigma*aspect_ratio (see size).""")
83
84 size = param.Number(default=0.155,doc="""
85 Overall size of the Gaussian, defined by:
86 exp(-x^2/(2*xsigma^2) - y^2/(2*ysigma^2)
87 where ysigma=size/2 and xsigma=size/2*aspect_ratio.""")
88
94
97 """
98 2D Exponential pattern generator.
99
100 Exponential decay based on distance from a central peak,
101 i.e. exp(-d), where d is the distance from the center (assuming
102 size=1.0 and aspect_ratio==1.0). More generally, the size and
103 aspect ratio determine the scaling of x and y dimensions:
104
105 yscale=size/2
106 xscale=yscale*aspect_ratio
107
108 The exponential is then computed for the given (x,y) values as::
109
110 exp(-sqrt((x/xscale)^2 - (y/yscale)^2))
111 """
112
113 aspect_ratio = param.Number(default=1/0.31,bounds=(0.0,None),softbounds=(0.0,2.0),
114 precedence=0.31,doc="""Ratio of the width to the height.""")
115
116 size = param.Number(default=0.155,doc="""
117 Overall scaling of the x and y dimensions.""")
118
124
127 """2D sine grating pattern generator."""
128
129 frequency = param.Number(default=2.4,bounds=(0.0,None),softbounds=(0.0,10.0),
130 precedence=0.50, doc="Frequency of the sine grating.")
131
132 phase = param.Number(default=0.0,bounds=(0.0,None),softbounds=(0.0,2*pi),
133 precedence=0.51,doc="Phase of the sine grating.")
134
136 """Return a sine grating pattern (two-dimensional sine wave)."""
137 return 0.5 + 0.5*sin(p.frequency*2*pi*self.pattern_y + p.phase)
138
139
140
141 -class Gabor(PatternGenerator):
142 """2D Gabor pattern generator."""
143
144 frequency = param.Number(default=2.4,bounds=(0.0,None),softbounds=(0.0,10.0),
145 precedence=0.50,doc="Frequency of the sine grating component.")
146
147 phase = param.Number(default=0.0,bounds=(0.0,None),softbounds=(0.0,2*pi),
148 precedence=0.51,doc="Phase of the sine grating component.")
149
150 aspect_ratio = param.Number(default=1.0,bounds=(0.0,None),softbounds=(0.0,2.0),
151 precedence=0.31,doc=
152 """
153 Ratio of pattern width to height.
154 The width of the Gaussian component is size*aspect_ratio (see Gaussian).
155 """)
156
157 size = param.Number(default=0.25,doc="""
158 Determines the height of the Gaussian component (see Gaussian).""")
159
166
167
168 -class Line(PatternGenerator):
169 """2D line pattern generator."""
170
171 thickness = param.Number(default=0.006,bounds=(0.0,None),softbounds=(0.0,1.0),
172 precedence=0.60,
173 doc="Thickness (width) of the solid central part of the line.")
174 smoothing = param.Number(default=0.05,bounds=(0.0,None),softbounds=(0.0,0.5),
175 precedence=0.61,
176 doc="Width of the Gaussian fall-off.")
177
180
181
182 -class Disk(PatternGenerator):
183 """
184 2D disk pattern generator.
185
186 An elliptical disk can be obtained by adjusting the aspect_ratio of a circular
187 disk; this transforms a circle into an ellipse by stretching the circle in the
188 y (vertical) direction.
189
190 The Gaussian fall-off at a point P is an approximation for non-circular disks,
191 since the point on the ellipse closest to P is taken to be the same point as
192 the point on the circle before stretching that was closest to P.
193 """
194
195 aspect_ratio = param.Number(default=1.0,bounds=(0.0,None),softbounds=(0.0,2.0),
196 precedence=0.31,doc=
197 "Ratio of width to height; size*aspect_ratio gives the width of the disk.")
198
199 size = param.Number(default=0.5,doc="Top to bottom height of the disk")
200
201 smoothing = param.Number(default=0.1,bounds=(0.0,None),softbounds=(0.0,0.5),
202 precedence=0.61,doc="Width of the Gaussian fall-off")
203
212
213
214 -class Ring(PatternGenerator):
215 """
216 2D ring pattern generator.
217
218 See the Disk class for a note about the Gaussian fall-off.
219 """
220
221 thickness = param.Number(default=0.015,bounds=(0.0,None),softbounds=(0.0,0.5),
222 precedence=0.60,doc="Thickness (line width) of the ring.")
223
224 smoothing = param.Number(default=0.1,bounds=(0.0,None),softbounds=(0.0,0.5),
225 precedence=0.61,doc="Width of the Gaussian fall-off inside and outside the ring.")
226
227 aspect_ratio = param.Number(default=1.0,bounds=(0.0,None),softbounds=(0.0,2.0),
228 precedence=0.31,doc=
229 "Ratio of width to height; size*aspect_ratio gives the overall width.")
230
231 size = param.Number(default=0.5)
232
240
243 """
244 Circular pattern for testing responses to differences in contrast.
245
246 The pattern contains a sine grating ring surrounding a sine grating disk, each
247 with parameters (orientation, size, scale and offset) that can be
248 changed independently.
249 """
250
251 orientationcenter = param.Number(default=0.0,bounds=(0.0,2*pi), doc="Orientation of the center grating.")
252 orientationsurround = param.Number(default=0.0,bounds=(0.0,2*pi), doc="Orientation of the surround grating.")
253 sizecenter = param.Number(default=0.5,bounds=(0.0,None),softbounds=(0.0,10.0), doc="Size of the center grating.")
254 sizesurround = param.Number(default=1.0,bounds=(0.0,None),softbounds=(0.0,10.0), doc="Size of the surround grating.")
255 scalecenter = param.Number(default=1.0,bounds=(0.0,None),softbounds=(0.0,10.0), doc="Scale of the center grating.")
256 scalesurround = param.Number(default=1.0,bounds=(0.0,None),softbounds=(0.0,10.0), doc="Scale of the surround grating.")
257 offsetcenter = param.Number(default=0.0,bounds=(0.0,None),softbounds=(0.0,10.0), doc="Offset of the center grating.")
258 offsetsurround = param.Number(default=0.0,bounds=(0.0,None),softbounds=(0.0,10.0), doc="Offset of the surround grating.")
259 smoothing = param.Number(default=0.0,bounds=(0.0,None),softbounds=(0.0,0.5), doc="Width of the Gaussian fall-off inside and outside the ring.")
260 thickness = param.Number(default=0.015,bounds=(0.0,None),softbounds=(0.0,0.5),doc="Thickness (line width) of the ring.")
261 aspect_ratio = param.Number(default=1.0,bounds=(0.0,None),softbounds=(0.0,2.0), doc="Ratio of width to height; size*aspect_ratio gives the overall width.")
262 size = param.Number(default=0.5)
263
264 - def __call__(self,**params_to_override):
265 p = ParamOverrides(self,params_to_override)
266 input_1=SineGrating(mask_shape=Disk(smoothing=0,size=1.0),phase=p.phase, frequency=p.frequency,
267 orientation=p.orientationcenter,
268 scale=p.scalecenter, offset=p.offsetcenter,
269 x=p.x, y=p.y,size=p.sizecenter)
270 input_2=SineGrating(mask_shape=Ring(thickness=p.thickness,smoothing=0,size=1.0),phase=p.phase, frequency=p.frequency,
271 orientation=p.orientationsurround, scale=p.scalesurround, offset=p.offsetsurround,
272 x=p.x, y=p.y, size=p.sizesurround)
273
274 patterns = [input_1(xdensity=p.xdensity,ydensity=p.ydensity,bounds=p.bounds),
275 input_2(xdensity=p.xdensity,ydensity=p.ydensity,bounds=p.bounds)]
276
277 image_array = numpy.add.reduce(patterns)
278 return image_array
279
283 """
284 2D rectangle pattern generator with no smoothing, for use when drawing
285 patterns pixel by pixel.
286 """
287
288 aspect_ratio = param.Number(default=1.0,bounds=(0.0,None),softbounds=(0.0,2.0),
289 precedence=0.31,doc=
290 "Ratio of width to height; size*aspect_ratio gives the width of the rectangle.")
291
292 size = param.Number(default=0.5,doc="Height of the rectangle.")
293
299
303 """2D rectangle pattern, with Gaussian smoothing around the edges."""
304
305 aspect_ratio = param.Number(default=1.0,bounds=(0.0,None),softbounds=(0.0,6.0),
306 precedence=0.31,doc=
307 "Ratio of width to height; size*aspect_ratio gives the width of the rectangle.")
308
309 size = param.Number(default=0.5,doc="Height of the rectangle.")
310
311 smoothing = param.Number(default=0.05,bounds=(0.0,None),softbounds=(0.0,0.5),
312 precedence=0.61,doc="Width of the Gaussian fall-off outside the rectangle.")
313
320
321
322
323 -class Arc(PatternGenerator):
324 """
325 2D arc pattern generator.
326
327 Draws an arc (partial ring) of the specified size (radius*2),
328 starting at radian 0.0 and ending at arc_length. The orientation
329 can be changed to choose other start locations. The pattern is
330 centered at the center of the ring.
331
332 See the Disk class for a note about the Gaussian fall-off.
333 """
334
335 aspect_ratio = param.Number(default=1.0,bounds=(0.0,None),softbounds=(0.0,6.0),
336 precedence=0.31,doc="""
337 Ratio of width to height; size*aspect_ratio gives the overall width.""")
338
339 thickness = param.Number(default=0.015,bounds=(0.0,None),softbounds=(0.0,0.5),
340 precedence=0.60,doc="Thickness (line width) of the ring.")
341
342 smoothing = param.Number(default=0.05,bounds=(0.0,None),softbounds=(0.0,0.5),
343 precedence=0.61,doc="Width of the Gaussian fall-off inside and outside the ring.")
344
345 arc_length = param.Number(default=pi,bounds=(0.0,None),softbounds=(0.0,2.0*pi),
346 inclusive_bounds=(True,False),precedence=0.62, doc="""
347 Length of the arc, in radians, starting from orientation 0.0.""")
348
349 size = param.Number(default=0.5)
350
357
360 """
361 2D curve pattern generator.
362
363 Based on Arc, but centered on a tangent point midway through the
364 arc, rather than at the center of a ring, and with curvature
365 controlled directly rather than through the overall size of the
366 pattern.
367
368 Depending on the size_type, the size parameter can control either
369 the width of the pattern, keeping this constant regardless of
370 curvature, or the length of the curve, keeping that constant
371 instead (as for a long thin object being bent).
372
373 Specifically, for size_type=='constant_length', the curvature
374 parameter determines the ratio of height to width of the arc, with
375 positive curvature for concave shape and negative for convex. The
376 size parameter determines the width of the curve.
377
378 For size_type=='constant_width', the curvature parameter
379 determines the portion of curve radian to 2pi, and the curve
380 radius is changed accordingly following the formula::
381
382 size=2pi*radius*curvature
383
384 Thus, the size parameter determines the total length of the
385 curve. Positive curvature stands for concave shape, and negative
386 for convex.
387
388 See the Disk class for a note about the Gaussian fall-off.
389 """
390
391
392 arc_length = param.Number(precedence=-1.0)
393 aspect_ratio = param.Number(default=1.0, precedence=-1.0)
394
395 size_type = param.ObjectSelector(default='constant_length',
396 objects=['constant_length','constant_width'],precedence=0.61,doc="""
397 For a given size, whether to draw a curve with that total length,
398 or with that width, keeping it constant as curvature is varied.""")
399
400 curvature = param.Number(default=0.5, bounds=(-0.5, 0.5), precedence=0.62, doc="""
401 Ratio of height to width of the arc, with positive value giving
402 a concave shape and negative value giving convex.""")
403
409
414 """Two 2D rectangle pattern generator."""
415
416 x1 = param.Number(default=-0.15,bounds=(-1.0,1.0),softbounds=(-0.5,0.5),
417 doc="X center of rectangle 1.")
418
419 y1 = param.Number(default=-0.15,bounds=(-1.0,1.0),softbounds=(-0.5,0.5),
420 doc="Y center of rectangle 1.")
421
422 x2 = param.Number(default=0.15,bounds=(-1.0,1.0),softbounds=(-0.5,0.5),
423 doc="X center of rectangle 2.")
424
425 y2 = param.Number(default=0.15,bounds=(-1.0,1.0),softbounds=(-0.5,0.5),
426 doc="Y center of rectangle 2.")
427
428
429
430
448
451 """2D squarewave grating pattern generator."""
452
453 frequency = param.Number(default=2.4,bounds=(0.0,None),softbounds=(0.0,10.0),
454 precedence=0.50,doc="Frequency of the square grating.")
455
456 phase = param.Number(default=0.0,bounds=(0.0,None),softbounds=(0.0,2*pi),
457 precedence=0.51,doc="Phase of the square grating.")
458
459
460
461
462
464 """
465 Return a square-wave grating (alternating black and white bars).
466 """
467 return around(0.5 + 0.5*sin(p.frequency*2*pi*self.pattern_y + p.phase))
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483 -class Sweeper(PatternGenerator):
518
521 """
522 PatternGenerator that accepts a list of other PatternGenerators.
523 To create a new pattern, asks each of the PatternGenerators in the
524 list to create a pattern, then it combines the patterns to create a
525 single pattern that it returns.
526 """
527
528
529
530
531 operator = param.Parameter(numpy.maximum,precedence=0.98,doc="""
532 Binary Numpy function used to combine the individual patterns.
533
534 Any binary Numpy array "ufunc" returning the same
535 type of array as the operands and supporting the reduce
536 operator is allowed here. Supported ufuncs include::
537
538 add
539 subtract
540 multiply
541 divide
542 maximum
543 minimum
544 remainder
545 power
546 logical_and
547 logical_or
548 logical_xor
549
550 The most useful ones are probably add and maximum, but there
551 are uses for at least some of the others as well (e.g. to
552 remove pieces of other patterns).
553
554 You can also write your own operators, by making a class that
555 has a static method named "reduce" that returns an array of the
556 same size and type as the arrays in the list. For example::
557
558 class return_first(object):
559 @staticmethod
560 def reduce(x):
561 return x[0]
562
563 """)
564
565 generators = param.List(default=[Constant(scale=0.0)],precedence=0.97,
566 class_=PatternGenerator,doc="""
567 List of patterns to use in the composite pattern. The default is
568 a blank pattern, and should thus be overridden for any useful work.""")
569
570 size = param.Number(default=1.0,doc="Scaling factor applied to all sub-patterns.")
571
572
574 """
575 Subclasses can override this method to provide constraints on
576 the values of generators' parameters and/or eliminate
577 generators from this list if necessary.
578 """
579 return p.generators
580
581
582
583
584
603
607 """
608 Generalized version of the Composite PatternGenerator that enforces spacing constraints
609 between pattern centers.
610
611 Currently supports minimum spacing, but can be generalized to
612 support maximum spacing also (and both at once).
613 """
614
615 min_separation = param.Number(default=0.0, bounds = (0,None),
616 softbounds = (0.0,1.0), doc="""
617 Minimum distance to enforce between all pairs of pattern centers.
618
619 Useful for ensuring that multiple randomly generated patterns
620 do not overlap spatially. Note that as this this value is
621 increased relative to the area in which locations are chosen,
622 the likelihood of a pattern appearing near the center of the
623 area will decrease. As this value approaches the available
624 area, the corners become far more likely to be chosen, due to
625 the distances being greater along the diagonals.
626 """)
627
628
629
630
631 max_trials = param.Integer(default = 50, bounds = (0,None),
632 softbounds = (0,100), precedence=-1, doc="""
633 Number of times to try for a new pattern location that meets the criteria.
634
635 This is an essentially arbitrary timeout value that helps
636 prevent an endless loop in case the requirements cannot be
637 met.""")
638
639
641 """
642 Returns true if the distance between the (x,y) locations of two generators
643 g0 and g1 is greater than a minimum separation.
644
645 Can be extended easily to support other criteria.
646 """
647 dist = sqrt((g1.x - g0.x) ** 2 +
648 (g1.y - g0.y) ** 2)
649 return dist >= p.min_separation
650
651
653 """
654 Advance the parameters for each generator for this presentation.
655
656 Picks a position for each generator that is accepted by __distance_valid
657 for all combinations. Returns a new list of the generators, with
658 some potentially omitted due to failure to meet the constraints.
659 """
660
661 valid_generators = []
662 for g in p.generators:
663
664 for trial in xrange(self.max_trials):
665
666
667 if alltrue([self.__distance_valid(g,v,p) for v in valid_generators]):
668 valid_generators.append(g)
669 break
670
671 vals = (g.force_new_dynamic_value('x'), g.force_new_dynamic_value('y'))
672
673 else:
674 self.warning("Unable to place pattern %s subject to given constraints" %
675 g.name)
676
677 return valid_generators
678
679
680
681 -class Selector(PatternGenerator):
682 """
683 PatternGenerator that selects from a list of other PatternGenerators.
684 """
685
686 generators = param.List(precedence=0.97,class_=PatternGenerator,bounds=(1,None),
687 default=[Disk(x=-0.3,aspect_ratio=0.5), Rectangle(x=0.3,aspect_ratio=0.5)],
688 doc="List of patterns from which to select.")
689
690 size = param.Number(default=1.0,doc="Scaling factor applied to all sub-patterns.")
691
692
693 index = param.Number(default=numbergen.UniformRandom(lbound=0,ubound=1.0,seed=76),
694 bounds=(-1.0,1.0),precedence=0.20,doc="""
695 Index into the list of pattern generators, on a scale from 0
696 (start of the list) to 1.0 (end of the list). Typically a
697 random value or other number generator, to allow a different item
698 to be selected each time.""")
699
700
702 """Selects and returns one of the patterns in the list."""
703 int_index=int(len(p.generators)*wrap(0,1.0,p.index))
704 pg=p.generators[int_index]
705
706 image_array = pg(xdensity=p.xdensity,ydensity=p.ydensity,bounds=p.bounds,
707 x=p.x+p.size*(pg.x*cos(p.orientation)-pg.y*sin(p.orientation)),
708 y=p.y+p.size*(pg.x*sin(p.orientation)+pg.y*cos(p.orientation)),
709 orientation=pg.orientation+p.orientation,size=pg.size*p.size,
710 scale=pg.scale*p.scale,offset=pg.offset+p.offset)
711
712 return image_array <