1 from __future__ import with_statement
2 """
3 Family of two-dimensional functions indexed by x and y.
4
5 All functions are written to be valid both for scalar x and y, and for
6 numpy arrays of x and y (in which case the result is also an array);
7 the functions therefore have the same mathematical behaviour as numpy.
8
9 $Id: patternfn.py 11295 2010-07-27 14:21:39Z ceball $
10 """
11 __version__='$Revision: 11295 $'
12
13
14
15 from math import pi
16
17 from numpy.oldnumeric import where,maximum,cos,sqrt,divide,greater_equal,bitwise_xor,exp
18 from numpy.oldnumeric import arcsin,logical_and,logical_or,less,minimum
19 from numpy import seterr
20
21 from contextlib import contextmanager
25 """
26 Many of the functions in this module use Gaussian smoothing, which
27 is based on a calculation like exp(divide(x*x,sigma)). When sigma
28 is zero the value of this expression should be zero at all points
29 in the plane, because such a Gaussian is infinitely small.
30 Obtaining the correct answer using finite-precision floating-point
31 array computations requires allowing infinite values to be
32 returned from divide(), and allowing exp() to underflow silently
33 to zero when given an infinite value. In numpy this is achieved
34 by using its seterr() function to disable divide-by-zero and
35 underflow warnings temporarily while these values are being
36 computed.
37 """
38 oldsettings=seterr(divide='ignore',under='ignore')
39 yield
40 seterr(**oldsettings)
41
44 """
45 Two-dimensional oriented Gaussian pattern (i.e., 2D version of a
46 bell curve, like a normal distribution but not necessarily summing
47 to 1.0).
48 """
49 if xsigma==0.0 or ysigma==0.0:
50 return x*0.0
51
52 with float_error_ignore():
53 x_w = divide(x,xsigma)
54 y_h = divide(y,ysigma)
55 return exp(-0.5*x_w*x_w + -0.5*y_h*y_h)
56
59 """
60 Sigmoid dividing axis into a positive and negative half,
61 with a smoothly sloping transition between them (controlled by the slope).
62
63 At default rotation, axis refers to the vertical (y) axis.
64 """
65 with float_error_ignore():
66 return (2.0 / (1.0 + exp(-2.0*slope*axis))) - 1.0
67
70 """
71 Two-dimensional oriented exponential decay pattern.
72 """
73 if xscale==0.0 or yscale==0.0:
74 return x*0.0
75
76 with float_error_ignore():
77 x_w = divide(x,xscale)
78 y_h = divide(y,yscale)
79 return exp(-sqrt(x_w*x_w+y_h*y_h))
80
81
82 -def gabor(x, y, xsigma, ysigma, frequency, phase):
83 """
84 Gabor pattern (sine grating multiplied by a circular Gaussian).
85 """
86 if xsigma==0.0 or ysigma==0.0:
87 return x*0.0
88
89 with float_error_ignore():
90 x_w = divide(x,xsigma)
91 y_h = divide(y,ysigma)
92 p = exp(-0.5*x_w*x_w + -0.5*y_h*y_h)
93 return p * 0.5*cos(2*pi*frequency*y + phase)
94
95
96
97
98
99
100
101 -def line(y, thickness, gaussian_width):
102 """
103 Infinite-length line with a solid central region, then Gaussian fall-off at the edges.
104 """
105 distance_from_line = abs(y)
106 gaussian_y_coord = distance_from_line - thickness/2.0
107 sigmasq = gaussian_width*gaussian_width
108
109 if sigmasq==0.0:
110 falloff = y*0.0
111 else:
112 with float_error_ignore():
113 falloff = exp(divide(-gaussian_y_coord*gaussian_y_coord,2*sigmasq))
114
115 return where(gaussian_y_coord<=0, 1.0, falloff)
116
117
118 -def disk(x, y, height, gaussian_width):
119 """
120 Circular disk with Gaussian fall-off after the solid central region.
121 """
122 disk_radius = height/2.0
123
124 distance_from_origin = sqrt(x**2+y**2)
125 distance_outside_disk = distance_from_origin - disk_radius
126 sigmasq = gaussian_width*gaussian_width
127
128 if sigmasq==0.0:
129 falloff = x*0.0
130 else:
131 with float_error_ignore():
132 falloff = exp(divide(-distance_outside_disk*distance_outside_disk,
133 2*sigmasq))
134
135 return where(distance_outside_disk<=0,1.0,falloff)
136
137
138 -def ring(x, y, height, thickness, gaussian_width):
139 """
140 Circular ring (annulus) with Gaussian fall-off after the solid ring-shaped region.
141 """
142 radius = height/2.0
143 half_thickness = thickness/2.0
144
145 distance_from_origin = sqrt(x**2+y**2)
146 distance_outside_outer_disk = distance_from_origin - radius - half_thickness
147 distance_inside_inner_disk = radius - half_thickness - distance_from_origin
148
149 ring = 1.0-bitwise_xor(greater_equal(distance_inside_inner_disk,0.0),greater_equal(distance_outside_outer_disk,0.0))
150
151 sigmasq = gaussian_width*gaussian_width
152
153 if sigmasq==0.0:
154 inner_falloff = x*0.0
155 outer_falloff = x*0.0
156 else:
157 with float_error_ignore():
158 inner_falloff = exp(divide(-distance_inside_inner_disk*distance_inside_inner_disk, 2.0*sigmasq))
159 outer_falloff = exp(divide(-distance_outside_outer_disk*distance_outside_outer_disk, 2.0*sigmasq))
160
161 return maximum(inner_falloff,maximum(outer_falloff,ring))
162
163
164 -def smooth_rectangle(x, y, rec_w, rec_h, gaussian_width_x, gaussian_width_y):
165 """
166 Rectangle with a solid central region, then Gaussian fall-off at the edges.
167 """
168
169 gaussian_x_coord = abs(x)-rec_w/2.0
170 gaussian_y_coord = abs(y)-rec_h/2.0
171
172 box_x=less(gaussian_x_coord,0.0)
173 box_y=less(gaussian_y_coord,0.0)
174 sigmasq_x=gaussian_width_x*gaussian_width_x
175 sigmasq_y=gaussian_width_y*gaussian_width_y
176
177 with float_error_ignore():
178 falloff_x=x*0.0 if sigmasq_x==0.0 else \
179 exp(divide(-gaussian_x_coord*gaussian_x_coord,2*sigmasq_x))
180 falloff_y=y*0.0 if sigmasq_y==0.0 else \
181 exp(divide(-gaussian_y_coord*gaussian_y_coord,2*sigmasq_y))
182
183 return minimum(maximum(box_x,falloff_x), maximum(box_y,falloff_y))
184
185
186
187 -def arc_by_radian(x, y, height, radian_range, thickness, gaussian_width):
188 """
189 Radial arc with Gaussian fall-off after the solid ring-shaped
190 region with the given thickness, with shape specified by the
191 (start,end) radian_range.
192 """
193
194
195 radius = height/2.0
196 half_thickness = thickness/2.0
197
198 distance_from_origin = sqrt(x**2+y**2)
199 distance_outside_outer_disk = distance_from_origin - radius - half_thickness
200 distance_inside_inner_disk = radius - half_thickness - distance_from_origin
201
202 ring = 1.0-bitwise_xor(greater_equal(distance_inside_inner_disk,0.0),greater_equal(distance_outside_outer_disk,0.0))
203
204 sigmasq = gaussian_width*gaussian_width
205
206 if sigmasq==0.0:
207 inner_falloff = x*0.0
208 outer_falloff = x*0.0
209 else:
210 with float_error_ignore():
211 inner_falloff = exp(divide(-distance_inside_inner_disk*distance_inside_inner_disk, 2.0*sigmasq))
212 outer_falloff = exp(divide(-distance_outside_outer_disk*distance_outside_outer_disk, 2.0*sigmasq))
213
214 output_ring = maximum(inner_falloff,maximum(outer_falloff,ring))
215
216
217
218
219
220
221
222
223
224
225
226 distance_from_origin += where(distance_from_origin == 0.0, 1e-5, 0)
227
228 with float_error_ignore():
229 sines = divide(y, distance_from_origin)
230 cosines = divide(x, distance_from_origin)
231 arcsines = arcsin(sines)
232
233 phase_1 = where(logical_and(sines >= 0, cosines >= 0), 2*pi-arcsines, 0)
234 phase_2 = where(logical_and(sines >= 0, cosines < 0), pi+arcsines, 0)
235 phase_3 = where(logical_and(sines < 0, cosines < 0), pi+arcsines, 0)
236 phase_4 = where(logical_and(sines < 0, cosines >= 0), -arcsines, 0)
237 arcsines = phase_1 + phase_2 + phase_3 + phase_4
238
239 if radian_range[0] <= radian_range[1]:
240 return where(logical_and(arcsines >= radian_range[0], arcsines <= radian_range[1]),
241 output_ring, 0.0)
242 else:
243 return where(logical_or(arcsines >= radian_range[0], arcsines <= radian_range[1]),
244 output_ring, 0.0)
245
246
247 -def arc_by_center(x, y, arc_box, constant_length, thickness, gaussian_width):
248 """
249 Arc with Gaussian fall-off after the solid ring-shaped region and specified
250 by point of tangency (x and y) and arc width and height.
251
252 This function calculates the start and end radian from the given width and
253 height, and then calls arc_by_radian function to draw the curve.
254 """
255
256 arc_w=arc_box[0]
257 arc_h=abs(arc_box[1])
258
259 if arc_w==0.0:
260 radius=0.0
261 angles=(0.0,0.0)
262 elif arc_h==0.0:
263 return smooth_rectangle(x, y, arc_w, thickness, 0.0, gaussian_width)
264 else:
265 if constant_length:
266 curvature=arc_h/arc_w
267 radius=arc_w/(2*pi*curvature)
268 angle=curvature*(2*pi)/2.0
269 else:
270 radius=arc_h/2.0+arc_w**2.0/(8*arc_h)
271 angle=arcsin(arc_w/2.0/radius)
272 if arc_box[1]<0:
273 y=y+radius
274 angles=(3.0/2.0*pi-angle, 3.0/2.0*pi+angle)
275 else:
276 y=y-radius
277 angles=(pi/2.0-angle, pi/2.0+angle)
278
279 return arc_by_radian(x, y, radius*2.0, angles, thickness, gaussian_width)
280