1 """GNUmed onscreen Snellen Chart emulator.
2
3 FIXME: store screen size
4 """
5
6
7
8 __version__ = "$Revision: 1.6 $"
9 __author__ = "Ian Haywood, Karsten Hilbert <Karsten.Hilbert@gmx.net>"
10 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
11
12 import math, random, sys, logging
13
14
15 import wx
16
17
18 if __name__ == '__main__':
19 sys.path.insert(0, '../../')
20 _ = lambda x:x
21 from Gnumed.pycommon import gmI18N
22
23
24 ID_SNELLENMENU = wx.NewId()
25 ID_SNELLENBUTTON = wx.NewId()
26
27
29
31 """Converts a pair of co-ordinates from block co-ords to real.
32
33 X, Y -- define top-left corner of current character
34 """
35 if self.mirror:
36 X = 5-X
37 return wx.Point(
38 int ((X * self.blockX) + self.startX),
39 int ((Y * self.blockY) + self.startY)
40 )
41
43 """
44 Draws the letter O
45 """
46 self._draw_arc (2.5, 2.5, 2.5, 0, 360)
47
49 self.O()
50 self._draw_line (2.6, 3, 4, 5)
51
53 if self.mirror:
54 self._draw_arc (2.5, 2.5, 2.5, 140, -140)
55 else:
56 self._draw_arc (2.5, 2.5, 2.5, 40, 320)
57
59 if self.mirror:
60 self._draw_arc (2.5, 2.5, 2.5, 140, -150)
61 else:
62 self._draw_arc (2.5, 2.5, 2.5, 40, 330)
63 self._draw_rect (2.5, 2.7, 5, 3.7)
64 self._draw_rect (4, 2.7, 5, 5)
65
67 self._draw_line (0, 0, 1, 5)
68 self._draw_line (2, 0, 1, 5)
69 self._draw_line (2, 0, 3, 5)
70 self._draw_line (4, 0, 3, 5)
71
73 self._draw_line (0, 0, 2, 5)
74 self._draw_line (4, 0, 2, 5)
75
77 self._draw_rect (0, 0, 5, 1)
78 self._draw_rect (2, 1, 3, 5)
79
81 self._draw_rect (2, 0, 3, 5)
82
84 self._draw_line (2, 0, 0, 5)
85 self._draw_line (2, 0, 4, 5)
86 self._draw_rect (1.4, 2.5, 3.6, 3.5)
87
89 self._draw_rect (0, 0, 1, 5)
90 self._draw_rect (1, 0, 5, 1)
91 self._draw_rect (1, 2, 5, 3)
92
94 self._draw_rect (0, 0, 1, 5)
95 self._draw_rect (0, 0, 5, 1)
96 self._draw_rect (0, 2, 5, 3)
97 self._draw_rect (0, 4, 5, 5)
98
100 self._draw_rect (4, 0, 5, 5)
101 self._draw_rect (0, 0, 5, 1)
102 self._draw_rect (0, 2, 5, 3)
103 self._draw_rect (0, 4, 5, 5)
104
106 self._draw_rect (0, 4, 5, 5)
107 self._draw_rect (0, 0, 1, 5)
108 self._draw_rect (2, 0, 3, 5)
109 self._draw_rect (4, 0, 5, 5)
110
112 self._draw_rect (0, 0, 5, 1)
113 self._draw_rect (0, 0, 1, 5)
114 self._draw_rect (2, 0, 3, 5)
115 self._draw_rect (4, 0, 5, 5)
116
118 self._draw_rect (0, 0, 1, 5)
119 self._draw_rect (4, 0, 5, 5)
120 self._draw_rect (1, 2, 4, 3)
121
123 self._draw_rect (0, 0, 1, 5)
124 self._draw_line (3.5, 0, 0.5, 2.5, width = 1.5)
125 self._draw_line (0.5, 2.5, 3.5, 5, width = 1.5)
126
128 self._draw_rect (0, 0, 1, 5)
129 self._draw_rect (1, 4, 5, 5)
130
132 self._draw_rect (0, 0, 5, 1)
133 self._draw_rect (0, 4, 5, 5)
134 self._draw_line (3.5, 1, 0, 4, width = 1.5)
135
137 self._draw_line (4, 0, 0, 5)
138 self._draw_line (0, 0, 4, 5)
139
141 """Sidebars common to N and M
142 """
143 self._draw_rect (0, 0, 1, 5)
144 self._draw_rect (4, 0, 5, 5)
145
147 self.NM ()
148 self._draw_line (0, 0, 4, 5)
149
151 self.NM ()
152 self._draw_line (4, 0, 0, 5)
153
155 self.NM ()
156 self._draw_line (0, 0, 2, 5)
157 self._draw_line (4, 0, 2, 5)
158
160 self._draw_rect (0, 0, 5, 1)
161 self._draw_rect (0, 0, 1, 5)
162
164 self._draw_line (2, 0, 0, 5)
165 self._draw_line (2, 0, 4, 5)
166 self._draw_rect (0.5, 4, 4.5, 5)
167
169 self._draw_rect (0, 0, 5, 1)
170 self._draw_rect (0, 0, 1, 5)
171 self._draw_rect (4, 0, 5, 5)
172
174 self._draw_rect (2, 0, 3, 5)
175 self._draw_rect (0, 2, 5, 3)
176
178 self._draw_rect (0, 0, 5, 1)
179 self._draw_rect (0, 4, 5, 5)
180 self._draw_rect (0, 1, 1, 4)
181 self._draw_rect (4, 1, 5, 4)
182
184 """
185 Star of 5 points
186 """
187 n = 5
188 list = []
189 for i in xrange (0, n):
190 theta = (i+0.00001)/n*2*math.pi
191 x = 2.5 + 2.5*math.sin (theta)
192 y = 2.5 - 2.5*math.cos (theta)
193 list.append (self.convert (x, y))
194 theta = (i+0.5)/n*2*math.pi
195 x = 2.5 + math.sin (theta)
196 y = 2.5 - math.cos (theta)
197 list.append (self.convert (x, y))
198 self.dc.DrawPolygon (list, fill_style = wx.WINDING_RULE)
199
200 latin = [A, C,
201 C, E, F, G,
202 H, I, K, L, M,
203 N, O, Q, T, V,
204 W, X, Z]
205
206 fourE = [E, UpE, DownE, BackE]
207
208 greek = [A, gamma, delta, E,
209 Z, H, I, K, M,
210 N, O, pi, T, X]
211
212 cyrillic = [A, delta, E, BackN,
213 K, M, H, O, pi,
214 T, C, X]
215
216 symbol = [O, cross, star, box]
217
218 alphabets = {
219 _("Latin"): latin,
220 _("Greek"): greek,
221 _("Cyrillic"): cyrillic,
222 _("Four Es"): fourE,
223 _("Symbol"): symbol
224 }
225
226 - def __init__(self, width, height, alpha = symbol, mirr = 0, parent = None):
227 """
228 Initialise. width and height define the physical size of the
229 CRT in cm.
230 """
231 wx.Frame.__init__ (self, parent, -1, _("Snellen Chart"))
232
233
234
235
236 self.screen_width_cm = width
237 self.screen_height_cm = height
238
239 self.screen_width_pixel = 0
240 self.screen_height_pixel = 0
241
242 self.standard_patient_chart_distances = [3, 5, 6, 7.5, 9, 12, 15, 18, 24, 30, 48, 60]
243 self.mirror = mirr
244 self.alphabet = alpha
245
246 wx.EVT_CLOSE (self, self.OnClose)
247 wx.EVT_KEY_DOWN (self, self.OnKeyUp)
248 wx.EVT_LEFT_UP (self, self.OnLeftDown)
249 wx.EVT_RIGHT_UP (self, self.OnRightDown)
250 wx.EVT_LEFT_DCLICK (self, self.OnDClick)
251 wx.EVT_PAINT (self, self.OnPaint)
252
253
254 self.ShowFullScreen(1)
255
256
257
258
259
260
262 self.dc = wx.PaintDC(self)
263 if self.screen_width_pixel == 0:
264 self.screen_width_pixel, self.screen_height_pixel = self.GetClientSizeTuple()
265 self.set_distance(2)
266
267
268
269 self.dc.SetFont(wx.Font (36, wx.ROMAN, wx.NORMAL, wx.NORMAL))
270 self.dc.SetBrush(wx.BLACK_BRUSH)
271 self.dc.SetBackground(wx.WHITE_BRUSH)
272 self.dc.SetPen(wx.TRANSPARENT_PEN)
273
274
275
276 self._swap_fg_bg_col()
277 self.dc.DrawRectangle(0, 0, self.screen_width_pixel, self.screen_height_pixel)
278 self._swap_fg_bg_col ()
279
280 self.dc.DrawText (str(self.standard_patient_chart_distances[self.distance]), 20, 20)
281 self.startX = self.spacing
282 for i in self.choices:
283 i (self)
284 self.startX += self.blockX*5
285 self.startX += self.spacing
286
287 self.dc = None
288
290 if key.GetKeyCode() == wx.WXK_UP and self.distance < len (self.standard_patient_chart_distances)-1:
291 self.set_distance (self.distance+1)
292 if key.GetKeyCode() == wx.WXK_DOWN and self.distance > 0:
293 self.set_distance (self.distance-1)
294 if key.GetKeyCode() == wx.WXK_ESCAPE:
295 self.Destroy ()
296
298 if self.distance > 0:
299 self.set_distance (self.distance-1)
300 self.Refresh ()
301
303 if self.distance < len(self.standard_patient_chart_distances)-1:
304 self.set_distance (self.distance+1)
305 self.Refresh()
306
309
312
314 import sys
315 sys.exit (0)
316
317
318
319
321 """Swap fore- and background pens."""
322 background = self.dc.GetBackground()
323 foreground = self.dc.GetBrush()
324 self.dc.SetBrush(background)
325 self.dc.SetBackground(foreground)
326
328 """Draw a rectangle."""
329 x1, y1 = self.convert (x1, y1)
330 x2, y2 = self.convert (x2, y2)
331
332 width = x2 - x1
333 if width < 0:
334 width = -width
335 x = x2
336 else:
337 x = x1
338
339 height = y2 - y1
340 if height < 0:
341 height = -height
342 y = y2
343 else:
344 y = y1
345
346 self.dc.DrawRectangle(x, y, width, height)
347
348 - def _draw_arc (self, x, y, arm, start, end):
349 """
350 Draws an arc-stroke, 1 unit wide, subtending (x, y), the outer
351 distance is arm, between start and end angle
352 """
353 topx, topy = self.convert (x-arm, y-arm)
354 botx, boty = self.convert (x+arm, y+arm)
355 width = botx - topx
356 if width < 0:
357 width = -width
358 t = botx
359 botx = topx
360 topx = t
361 height = boty - topy
362 self.dc.DrawEllipticArc(topx, topy, width, height, start, end)
363
364 arm -= 1
365 self._swap_fg_bg_col()
366 topx, topy = self.convert (x-arm, y-arm)
367 botx, boty = self.convert (x+arm, y+arm)
368 width = botx - topx
369 if width < 0:
370 width = -width
371 t = botx
372 botx = topx
373 topx = t
374 height = boty- topy
375 self.dc.DrawEllipticArc(topx, topy, width, height, start, end)
376 self._swap_fg_bg_col()
377
379 """Draws straight descending letter-stroke.
380
381 (x1, y1) is top-left,
382 (x2, y2) is bottom left point
383 """
384 coords = [
385 self.convert (x1, y1),
386 self.convert (x1+width, y1),
387 self.convert (x2+width, y2),
388 self.convert (x2, y2)
389 ]
390 self.dc.DrawPolygon(coords)
391
392
394 """
395 Sets standard viewing distance, against which patient is
396 compared. n is an index to the list self.standard_patient_chart_distances
397 """
398 self.distance = n
399
400
401
402
403 one_minute = (math.pi / 180) / 60
404 blocksize = (self.standard_patient_chart_distances[n] * 100) * math.atan(one_minute)
405
406 self.blockX = int (blocksize / self.screen_width_cm * self.screen_width_pixel)
407 self.blockY = int (blocksize / self.screen_height_cm * self.screen_height_pixel)
408
409 chars = int (self.screen_width_pixel / (self.blockX*5)) - 1
410 if chars < 1:
411 chars = 1
412 if chars > 7:
413 chars = 7
414 if chars < len (self.alphabet):
415 self.choices = []
416 while len (self.choices) < chars:
417 c = random.choice (self.alphabet)
418 if not c in self.choices:
419 self.choices.append (c)
420 else:
421 self.choices = [ random.choice(self.alphabet) for i in range(1, chars) ]
422 self.spacing = int ((self.screen_width_pixel -
423 (chars*self.blockX*5))/(chars+1))
424 if self.spacing < 0:
425 self.spacing = 0
426 self.startY = int ((self.screen_height_pixel-(self.blockY*5))/2)
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
453 """
454 Dialogue class to get Snellen chart settings.
455 """
457 wx.Dialog.__init__(
458 self,
459 None,
460 -1,
461 _("Snellen Chart Setup"),
462 wx.DefaultPosition,
463 wx.Size(350, 200)
464 )
465
466
467
468
469 vbox = wx.BoxSizer (wx.VERTICAL)
470 hbox1 = wx.BoxSizer (wx.HORIZONTAL)
471 hbox1.Add (wx.StaticText(self, -1, _("Screen Height (cm): ")), 0, wx.ALL, 15)
472 self.height_ctrl = wx.SpinCtrl (self, -1, value = "25", min = 10, max = 100)
473 hbox1.Add (self.height_ctrl, 1, wx.TOP, 15)
474 vbox.Add (hbox1, 1, wx.EXPAND)
475 hbox2 = wx.BoxSizer (wx.HORIZONTAL)
476 hbox2.Add (wx.StaticText(self, -1, _("Screen Width (cm): ")), 0, wx.ALL, 15)
477 self.width_ctrl = wx.SpinCtrl (self, -1, value = "30", min = 10, max = 100)
478 hbox2.Add (self.width_ctrl, 1, wx.TOP, 15)
479 vbox.Add (hbox2, 1, wx.EXPAND)
480 hbox3 = wx.BoxSizer (wx.HORIZONTAL)
481 hbox3.Add (wx.StaticText(self, -1, _("Alphabet: ")), 0, wx.ALL, 15)
482 self.alpha_ctrl = wx.Choice (self, -1, choices = cSnellenChart.alphabets.keys ())
483 hbox3.Add (self.alpha_ctrl, 1, wx.TOP, 15)
484 vbox.Add (hbox3, 1, wx.EXPAND)
485 self.mirror_ctrl = wx.CheckBox (self, -1, label = _("Mirror"))
486 vbox.Add (self.mirror_ctrl, 0, wx.ALL, 15)
487 vbox.Add (wx.StaticText (self, -1,
488 _("""Control Snellen chart using mouse:
489 left-click increases text
490 right-click decreases text
491 double-click ends""")), 0, wx.ALL, 15)
492 hbox5 = wx.BoxSizer (wx.HORIZONTAL)
493 ok = wx.Button(self, wx.ID_OK, _(" OK "), size=wx.DefaultSize)
494 cancel = wx.Button (self, wx.ID_CANCEL, _(" Cancel "),
495 size=wx.DefaultSize)
496 hbox5.Add (ok, 1, wx.TOP, 15)
497 hbox5.Add (cancel, 1, wx.TOP, 15)
498 vbox.Add (hbox5, 1, wx.EXPAND)
499 self.SetSizer (vbox)
500 self.SetAutoLayout (1)
501 vbox.Fit (self)
502
503 wx.EVT_BUTTON (ok, wx.ID_OK, self.OnOK)
504 wx.EVT_BUTTON (cancel, wx.ID_CANCEL, self.OnCancel)
505 wx.EVT_CLOSE (self, self.OnClose )
506
507
508
510 self.EndModal (wx.ID_CANCEL)
511
513 self.EndModal (wx.ID_CANCEL)
514
515 - def OnOK (self, event):
516
517 selected_alpha_string = self.alpha_ctrl.GetStringSelection()
518 if selected_alpha_string is None or len (selected_alpha_string) < 2:
519 alpha = cSnellenChart.latin
520 else:
521 alpha = cSnellenChart.alphabets[selected_alpha_string]
522 height = self.height_ctrl.GetValue ()
523 width = self.width_ctrl.GetValue ()
524 mirr = self.mirror_ctrl.GetValue()
525 self.vals = (width, height, alpha, mirr)
526 self.EndModal(wx.ID_OK)
527
528
529
530
531
532
533 if __name__ == '__main__':
534
535 gmI18N.activate_locale()
536 gmI18N.install_domain('gnumed')
537
540 cfg = cSnellenCfgDlg()
541 if cfg.ShowModal () == 0:
542 frame = cSnellenChart (
543 width = cfg.vals[0],
544 height = cfg.vals[1],
545 alpha = cfg.vals[2],
546 mirr = cfg.vals[3],
547 parent = None
548 )
549 frame.CentreOnScreen(wx.BOTH)
550 self.SetTopWindow(frame)
551 frame.Destroy = frame.DestroyWhenApp
552 frame.Show(True)
553 return 1
554
556 app = TestApp ()
557 app.MainLoop ()
558
559 main()
560
561