1
2
3 """Supplemental sprite functions.
4
5 These are mostly generator functions that fill sprites with
6 useful pixel patterns: gratings of various times, Gaussian
7 envelopes etc. It extends the basic functionality provided
8 by the sprite module and most functions work on existing
9 Sprite objects by modding the underlying image data.
10
11 Author -- James A. Mazer (mazerj@gmail.com)
12
13 """
14
15 import numpy as np
16 import sprite
17
19
20
21
22
23
24
26
27
28
29
30
31
32 if color:
33 R, G, B = color
34 else:
35 try:
36 R, G, B = np.array(R) / 255.0
37 except TypeError:
38 pass
39 except ValueError:
40 pass
41 return R, G, B
42
44 """Convert an [WxHx1] grayscale image into a [WxHx3] RGB array.
45
46 :param a: (array) monochrome input image
47
48 :return: (array) RGB output image
49
50 """
51 return np.transpose(np.array([a, a, a]), axes=[1,2,0])
52
54 """Convert a floating point array into an np.uint8 array.
55
56 :param a: (array) array to be converted
57
58 :param rgb: if true, then promote from 1 plane to 3 planes using g2rgb
59
60 :param norm: (boolean) if true, scale min-max into range 1-255
61
62 :return: pixelized version of input array; result is suitable for
63 assigning to <sprite>.alpha or <sprite>.array.
64
65 """
66 if norm:
67 amin = min(ravel(a))
68 amax = max(ravel(a))
69 a = (1.0 + 254.0 * ((a - amin) / (amax - amin))).astype(np.uint8)
70 else:
71 a = a.astype(np.uint8)
72 if rgb is None:
73 return a
74 else:
75 return g2rgb(a)
76
77 -def genpolar(w, h=None, typecode=np.float64, degrees=False):
78 """Generate polar axes (like polar meshgrid)
79
80 :param w, h: width and height of sprite (height defaults to width)
81
82 :param typecode: output type, defaults to float64 ('d')
83
84 :param degrees: True/False
85
86 :return: (array) r and theta arrays
87
88 """
89 x, y = sprite.genaxes(w, h, w, h)
90 r = np.hypot(x,y).astype(typecode)
91 if degrees:
92 t = (180.0 * np.arctan2(y, x) / np.pi).astype(typecode)
93 else:
94 t = np.arctan2(y, x).astype(typecode)
95 return r, t
96
98 """Convert grayscale image array to 8bit integer array.
99
100 :param g: (numpy array) gray scale image array
101
102 :param inrange: (float pair) min/max values for input image
103
104 :return: (numpy array) uint8 RGB array
105
106 """
107
108 minval, maxval = inrange
109 a = 255.0 * (g - minval) / (maxval - minval)
110 return np.transpose(np.array((a,a,a,)).astype(np.uint8), axes=[1,2,0])
111
112 -def rgb2rgb8(r, g, b, inrange=(-1.0, 1.0)):
113 """Convert rgb image data to 8bit integer array.
114
115 :param r,g,b: (numpy arrays) red, green and blue image planes
116
117 :param inrange: (float pair) min/max values for input image
118
119 :return: (numpy array) uint8 RGB array
120
121 """
122
123 minval, maxval = inrange
124 a = np.array((r, g, b,))
125 a = 255.0 * (a - minval) / (maxval - minval)
126 return np.transpose(a.astype(np.uint8), axes=[1,2,0])
127
128 -def singrat(s, frequency, phase_deg, ori_deg, R=1.0, G=1.0, B=1.0,
129 meanlum=0.5, moddepth=1.0, ppd=None, color=None):
130 """2D sine grating generator (odd symmetric).
131
132 *NB* Verified frequency is really cycles/sprite JM 17-sep-2006.
133
134 :param s: (Sprite) target sprite
135
136 :param frequency: frequency in cycles/sprite (or cyc/deg, if ppd
137 is given)
138
139 :param phase_deg: (degrees) (nb: 0deg phase centers the sine
140 function at sprite ctr)
141
142 :param ori_deg: (degrees) grating orientation
143
144 :param R,G,B: (either R is colortriple or R,G,B are 0-1 values)
145
146 :param ppd: (pixels/degree) if specified, then it means that freq
147 is being specified in cycles/degree
148
149 :param meanlum: mean (DC) value of grating (0-1); default is 0.5
150
151 :param moddepth: modulation depth (0-1)
152
153 :param color: RGB triple (alternative specification of color vector)
154
155 :return: nothing (works in place)
156
157 """
158
159 if s.w != s.h:
160 raise SpritetoolsError, 'sprite must be square'
161
162 if not ppd is None:
163
164 frequency = s.w / ppd * frequency
165 meanlum = 256.0 * meanlum
166 moddepth = 127.0 * moddepth
167
168 R, G, B = unpack_rgb(color, R, G, B)
169 r = np.hypot(s.xx/s.w, s.yy/s.h)
170 t = np.arctan2(s.yy, s.xx)
171 t = t - (np.pi * ori_deg) / 180.
172 x, y = (r * np.cos(t), r * np.sin(t))
173
174 i = moddepth * np.sin((2.0 * np.pi * frequency * x) -
175 (np.pi * phase_deg / 180.0))
176 s.array[::] = np.transpose((np.array((R*i,G*i,B*i)) +
177 meanlum).astype(np.uint8),
178 axes=[1,2,0])
179
180 -def cosgrat(s, frequency, phase_deg, ori_deg, R=1.0, G=1.0, B=1.0,
181 meanlum=0.5, moddepth=1.0, ppd=None, color=None):
182 """2D cosine grating generator (even symmetric).
183
184 *NB* Verified frequency is really cycles/sprite JM 17-sep-2006.
185
186 :param s: (Sprite) target sprite
187
188 :param frequency: frequency in cycles/sprite
189
190 :param phase_deg: (degrees) (nb: 0deg phase centers the cosine
191 function at sprite ctr)
192
193 :param ori_deg: (degrees) grating orientation
194
195 :param R,G,B: (either R is colortriple or R,G,B are 0-1 values)
196
197 :param ppd: (pixels/degree) if specified, then it means that freq
198 is being specified in cycles/degree
199
200 :param meanlum: mean (DC) value of grating (0-1); default is 0.5
201
202 :param moddepth: modulation depth (0-1)
203
204 :param color: RGB triple (alternative specification of color vector)
205
206 :return: nothing (works in place)
207
208 """
209 return singrat(s, frequency, phase_deg - 90.0, ori_deg,
210 R=R, G=G, B=B, meanlum=meanlum, moddepth=moddepth,
211 ppd=ppd, color=color)
212
213 -def singrat2(s, frequency, phase_deg, ori_deg, R=1.0, G=1.0, B=1.0,
214 meanlum=0.5, moddepth=1.0, ppd=None, color=None, xcache=None):
215 """CACHING version of singrat
216
217 This is identical to singrat(), but will cache the coordinate
218 system (based on ori_deg) in a dictionary for fast retrival. This
219 can really speed things up when generating a large number of
220 gratings that differ only in phase or sf.
221
222 If you don't need caching, don't use this!
223
224 :param xcache: (dict) the actual cache
225
226 :return: (dict) updated cache
227
228 """
229
230 if s.w != s.h:
231 raise SpritetoolsError, 'sprite must be square'
232
233 if not ppd is None:
234
235 frequency = s.w / ppd * frequency
236 meanlum = 256.0 * meanlum
237 moddepth = 127.0 * moddepth
238
239 R, G, B = unpack_rgb(color, R, G, B)
240
241
242
243
244 if (xcache is None) or (ori_deg not in xcache):
245 r = np.hypot(s.xx/s.w, s.yy/s.h)
246 t = np.arctan2(s.yy, s.xx)
247 t = t - (np.pi * ori_deg) / 180.
248 x = r * np.cos(t)
249 if xcache is None:
250 xcache = dict()
251 xcache[ori_deg] = x
252 else:
253 x = xcache[ori_deg]
254
255
256 i = moddepth * np.sin((2.0 * np.pi * frequency * x) -
257 (np.pi * phase_deg / 180.0))
258 s.array[::] = np.transpose((np.array((R*i,G*i,B*i)) +
259 meanlum).astype(np.uint8),
260 axes=[1,2,0])
261 return xcache
262
263 -def cosgrat2(s, frequency, phase_deg, ori_deg, R=1.0, G=1.0, B=1.0,
264 meanlum=0.5, moddepth=1.0, ppd=None, color=None, xcache=None):
265 """CACHING version of cosgrat
266
267 This is identical to cosgrat(), but will cache the coordinate
268 system (based on ori_deg) in a dictionary for fast retrival. This
269 can really speed things up when generating a large number of
270 gratings that differ only in phase or sf.
271
272 If you don't need caching, don't use this!
273
274 :param xcache: (dict) the actual cache
275
276 :return: (dict) updated cache
277
278 """
279
280 return singrat2(s, frequency, phase_deg - 90.0, ori_deg,
281 R=R, G=G, B=B, meanlum=meanlum, moddepth=moddepth,
282 ppd=ppd, color=color)
283
284 -def polargrat(s, cfreq, rfreq, phase_deg, polarity,
285 R=1.0, G=1.0, B=1.0, logpolar=False,
286 meanlum=0.5, moddepth=1.0, ppd=None, color=None):
287 """2D polar (non-Cartesian) grating generator.
288
289 *NB* Verified frequencies are really cycles/sprite JM 17-sep-2006.
290
291 :param s: (Sprite) target sprite
292
293 :param cfreq: concentric frequency (cycles/sprite or cyc/deg - see ppd)
294
295 :param rfreq: concentric frequency (cycles/360deg)
296
297 :param phase_deg: (degrees)
298
299 :param polarity: 0 or 1 -> really just a 180 deg phase shift
300
301 :param R,G,B: (either R is colortriple or R,G,B are 0-1 values)
302
303 :param ppd: (pixels/degree); if specified, then it means that freq
304 is being specified in cycles/degree - for cfreq only
305
306 :param meanlum: mean (DC) value of grating (0-1); default is 0.5
307
308 :param moddepth: modulation depth (0-1)
309
310 :param color: RGB triple (alternative specification of color vector)
311
312 """
313
314 if s.w != s.h:
315 raise SpritetoolsError, 'sprite must be square'
316
317 if not ppd is None:
318
319 cfreq = s.w / ppd * cfreq
320 meanlum = 256.0 * meanlum
321 moddepth = 127.0 * moddepth
322
323 R, G, B = unpack_rgb(color, R, G, B)
324 if polarity < 0:
325 polarity = -1.0
326 else:
327 polarity = 1.0
328 x, y = (polarity * s.xx/s.w, s.yy/s.h)
329
330 if logpolar:
331 z = (np.log(np.hypot(x,y)) * cfreq) + (np.arctan2(y,x) * rfreq /
332 (2.0 * np.pi))
333 else:
334 z = (np.hypot(y,x) * cfreq) + (np.arctan2(y,x) * rfreq / (2.0 * np.pi))
335 i = moddepth * np.cos((2.0 * np.pi * z) - (np.pi * phase_deg / 180.0))
336 s.array[::] = np.transpose((np.array((R*i,G*i,B*i)) +
337 meanlum).astype(np.uint8),
338 axes=[1,2,0])
339
340 -def logpolargrat(s, cfreq, rfreq, phase_deg, polarity,
341 R=1.0, G=1.0, B=1.0,
342 meanlum=0.5, moddepth=1.0, ppd=None, color=None):
343 """2D log polar (non-Cartesian) grating generator
344
345 *NB* Frequencies are in cycles/sprite or cycles/360deg
346
347 *NB* Verified frequenies are really cycles/sprite JM 17-sep-2006.
348
349 :param s: (Sprite) target sprite
350
351 :param cfreq: concentric frequency (cycles/sprite or cycles/deg see ppd)
352
353 :param rfreq: concentric frequency (cycles/360deg)
354
355 :param phase_deg: (degrees)
356
357 :param polarity: 0 or 1 -> really just a 180 deg phase shift
358
359 :param R,G,B: (either R is colortriple or R,G,B are 0-1 values)
360
361 :param ppd: (pixels/degree) if specfied, then it means that freq
362 is being specified in cycles/degree
363
364 :param meanlum: meanlum (DC) value of grating (0-1); default is 0.5
365
366 :param moddepth: modulation depth (0-1)
367
368 :param color: RGB triple (alternative specification of color vector)
369
370 :return: nothing (works in place)
371
372 """
373 polargrat(s, cfreq, rfreq, phase_deg, polarity,
374 R=R, G=G, B=B, logpolar=1,
375 meanlum=meanlum, moddepth=moddepth, ppd=ppd)
376
377 -def hypergrat(s, freq, phase_deg, ori_deg,
378 R=1.0, G=1.0, B=1.0,
379 meanlum=0.5, moddepth=1.0, ppd=None, color=None):
380 """2D hyperbolic (non-Cartesian) grating generator.
381
382 *NB* frequencies are in cycles/sprite or cycles/360deg
383
384 *NB* verified frequencies are really cycles/sprite JM 17-sep-2006
385
386 :param s: (Sprite) target sprite
387
388 :param freq: frequency (cycles/sprite or cyc/deg - see ppd)
389
390 :param phase_deg: (degrees)
391
392 :param ori_deg: (degrees) orientation
393
394 :param R,G,B: (either R is colortriple or R,G,B are 0-1 values)
395
396 :param ppd: (pixels/deg) if specified, then it means that freq is
397 being specified in cycles/degreee
398
399 :param meanlum: mean (DC) value of grating (0-1); default is 0.5
400
401 :param moddepth: modulation depth (0-1)
402
403 :param color: RGB triple (alternative specification of color vector)
404
405 :return: nothing (works in place)
406
407 """
408
409 if s.w != s.h:
410 raise SpritetoolsError, 'sprite must be square'
411
412 if not ppd is None:
413
414 freq = s.w / ppd * freq
415 meanlum = 256.0 * meanlum
416 moddepth = 127.0 * moddepth
417
418 R, G, B = unpack_rgb(color, R, G, B)
419 r = np.hypot(s.xx / s.w, s.yy / s.h)
420 t = np.arctan2(s.yy, s.xx) - (np.pi * ori_deg) / 180.0
421 x, y = (r * np.cos(t), r * np.sin(t))
422
423 z = np.sqrt(np.fabs((x * freq) ** 2 - (y * freq) ** 2))
424 i = moddepth * np.cos((2.0 * np.pi * z) - (np.pi * phase_deg / 180.0))
425 s.array[::] = np.transpose((np.array((R*i,G*i,B*i)) +
426 meanlum).astype(np.uint8), axes=[1,2,0])
427
428
429 -def hartley(s, kx, ky, R=1.0, G=1.0, B=1.0,
430 meanlum=0.5, moddepth=1.0, color=None):
431 """Hartley basis function generator (after Ringach et al, 1997)
432
433 :param s: (Sprite) target sprite
434
435 :param kx, ky: combind ori/sf/phase params (see paper) these are
436 basically in cycles/sprite, so the nyquist limit is size/2..
437
438 :param R,G,B: (either R is colortriple or R,G,B are 0-1 values)
439
440 :param meanlum: mean (DC) value of grating (0-1); default is 0.5
441
442 :param moddepth: modulation depth (0-1)
443
444 :param color: RGB triple (alternative specification of color vector)
445
446 :return: nothing (works in place)
447
448 """
449
450 if s.w != s.h:
451 raise SpritetoolsError, 'sprite must be square'
452
453 meanlum = 256.0 * meanlum
454 moddepth = 127.0 * moddepth
455 R, G, B = unpack_rgb(color, R, G, B)
456
457 M = s.w
458 l = s.xx + M/2
459 m = s.yy + M/2
460 t = 2.0 * np.pi * ((kx * l) + (ky * m)) / M
461 i = moddepth * (np.sin(t) + np.cos(t)) / np.sqrt(2.0)
462 s.array[::] = np.transpose((np.array((R*i,G*i,B*i)) +
463 meanlum).astype(np.uint8),
464 axes=[1,2,0])
465
466 -def gabor(s, frequency, phase_deg, ori_deg, sigma,
467 R=1.0, G=1.0, B=1.0,
468 meanlum=0.5, moddepth=1.0, ppd=None, color=None):
469 """2D gabor generator.
470
471 :param s: (Sprite) target sprite
472
473 :param frequency: frequency in cycles/sprite (or cyc/deg, if ppd
474 is given)
475
476 :param phase_deg: (degrees) (nb: 0deg phase centers the sine
477 function at sprite ctr)
478
479 :param ori_deg: (degrees) grating orientation
480
481 :param sigma: (pixels) envelope width -- sprite should be at
482 least 6*sigma by 6*sigma in width and height
483
484 :param R,G,B: (either R is colortriple or R,G,B are 0-1 values)
485
486 :param ppd: (pixels/degree) if specified, then it means that freq
487 is being specified in cycles/degree
488
489 :param meanlum: mean (DC) value of grating (0-1); default is 0.5
490
491 :param moddepth: modulation depth (0-1)
492
493 :param color: RGB triple (alternative specification of color vector)
494
495 :return: nothing (works in place)
496
497 """
498
499 if s.w != s.h:
500 raise SpritetoolsError, 'sprite must be square'
501
502 if not ppd is None:
503
504 frequency = frequency * s.w / ppd
505 meanlum = 256.0 * meanlum
506 moddepth = 127.0 * moddepth
507
508 R, G, B = unpack_rgb(color, R, G, B)
509
510 gamma = 1.0
511 sf = frequency / s.w
512 lambda_ = 1.0 / sf
513 theta = np.pi * ori_deg / 180.0
514 phi = np.pi * phase_deg / 180.0
515
516 x = (s.xx * np.cos(theta)) + (s.yy * np.sin(theta))
517 y = (-s.xx * np.sin(theta)) + (s.yy * np.cos(theta))
518 g = np.exp(-((x**2) + ((gamma**2) * y**2))/(2 * (sigma**2))) * \
519 np.cos((2.0 * np.pi * x / lambda_) - phi)
520 i = moddepth * g
521 s.array[::] = np.transpose((np.array((R*i,G*i,B*i)) +
522 meanlum).astype(np.uint8),
523 axes=[1,2,0])
524
525
554
555 -def gaussiannoise(s, R=1.0, G=1.0, B=1.0, meanlum=0.5, stddev=1.0, color=None):
556 """Fill sprite with Gaussian white noise
557
558 :param s: (Sprite) target sprite
559
560 :param R,G,B: (either R is colortriple or R,G,B are 0-1 values)
561
562 :param meanlum: mean (DC) value of grating (0-1); default is 0.5
563
564 :param stddev: std of gaussian distribution (0-1)
565
566 :param color: RGB triple (alternative specification of color vector)
567
568 :return: nothing (works in place)
569
570 """
571
572
573 R, G, B = unpack_rgb(color, R, G, B)
574 i = np.random.normal(meanlum, stddev, size=(s.w, s.h))
575 i = (255.0 * i).astype(np.uint8)
576 s.array[::] = np.transpose(np.array((R*i,G*i,B*i)), axes=[1,2,0])
577
578
579 -def alphabar(s, bw, bh, ori_deg, R=1.0, G=1.0, B=1.0):
580 """Generate a bar into existing sprite using the alpha channel.
581
582 This fills the sprite with 'color' and then puts a [bw x bh] transparent
583 bar of the specified orientation in the alpha channel.
584
585 :param s: Sprite()
586
587 :param bw,bh: (pixels) bar width and height
588
589 :param ori_deg: (degrees) bar orientation
590
591 :param R,G,B: (either R is colortriple or R,G,B are 0-1 values)
592
593 :return: nothing (works in place)
594
595 """
596 R, G, B = (np.array(unpack_rgb(None, R, G, B)) * 255.0).astype(np.int)
597 r, t = genpolar(s.w, s.h, degrees=True)
598 t += ori_deg
599 x = r * np.cos(t)
600 y = r * np.sin(t)
601 s.fill((R,G,B))
602 mask = np.where(np.less(abs(x), (bw/2.0)) * np.less(np.abs(y), (bh/2.0)),
603 255, 0)
604 s.alpha[::] = mask[::].astype(np.uint8)
605
607 """Generate symmetric and asymmetric Gaussian envelopes
608 into the alpha channel.
609
610 *NB* alpha's have peak value of fully visible (255), low end
611 depends on sigma
612
613 :param s: (Sprite)
614
615 :param xsigma: (pixels) standard dev (think of this as the
616 Gaussian's generated with ori=0 and then rotated)
617
618 :param ysigma: (pixels) if None, then use xsigma (symmatric)
619
620 :param ori_deg: (degrees) orientation of Gaussian
621
622 :return: nothing (works in place)
623
624 """
625 if ysigma is None:
626 ysigma = xsigma
627 r = np.hypot(s.xx, s.yy)
628 t = np.arctan2(s.yy, s.xx) - (np.pi * ori_deg) / 180.0
629 x, y = (r * np.cos(t), r * np.sin(t))
630 i = 255.0 * np.exp(-(x**2) / (2*xsigma**2)) * np.exp(-(y**2) / (2*ysigma**2))
631 s.alpha[::] = i[::].astype(np.uint8)
632
633
634
635
636 from pypeerrors import obsolete_fn
637 alphaGaussian = alpha_gaussian
638 alphaGaussian2 = obsolete_fn
639 alphaGaussian2 = obsolete_fn
640 gaussianEnvelope = obsolete_fn
641
642
644 import time
645 from sprite import Sprite
646 s = Sprite(250, 250, 0, 0, fb=fb, on=1)
647 s2 = Sprite(250, 250, 0, 0, fb=fb, on=1)
648 nmax = 100
649
650 t0 = time.time()
651 for n in range(nmax):
652 singrat(s, 10, 0.0, n, R=1.0, G=1.0, B=1.0,
653 meanlum=0.5, moddepth=1.0)
654 s.blit(flip=1)
655 time.time()
656 print 'all', float(nmax) / (time.time() - t0), 'fps'
657
658 t0 = time.time()
659 for n in range(nmax):
660 singrat(s, 10, 0.0, n, R=1.0, G=1.0, B=1.0,
661 meanlum=0.5, moddepth=1.0)
662 time.time()
663 print 'compute only', float(nmax) / (time.time() - t0), 'fps'
664
665 s2 = Sprite(250, 250, 0, 0, fb=fb, on=1)
666 singrat(s2, 10, 0.0, 0, R=1.0, G=1.0, B=1.0,
667 meanlum=0.5, moddepth=1.0)
668 foo = s2.array[::]
669 bar = s2.array[::]
670 t0 = time.time()
671 for n in range(nmax):
672 foo[::] = bar[::]
673 s.blit(flip=1)
674 time.time()
675 print 'copy+blit', float(nmax) / (time.time() - t0), 'fps'
676
677 t0 = time.time()
678 for n in range(nmax):
679 s.blit(flip=1)
680 time.time()
681 print 'blit only', float(nmax) / (time.time() - t0), 'fps'
682
683
684
685 if __name__ == '__main__':
686 sys.stderr.write('%s should never be loaded as main.\n' % __file__)
687 sys.exit(1)
688