Package Gnumed :: Package wxpython :: Module gmSnellen
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmSnellen

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