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          _ = lambda x:x 
 21  from Gnumed.pycommon import gmI18N 
 22  #from Gnumed.wxpython import gmPlugin 
 23   
 24  ID_SNELLENMENU = wx.NewId() 
 25  ID_SNELLENBUTTON = wx.NewId() 
 26   
 27  #============================================================================ 
28 -class cSnellenChart(wx.Frame):
29
30 - def convert (self, X,Y):
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
42 - def O (self):
43 """ 44 Draws the letter O 45 """ 46 self._draw_arc (2.5, 2.5, 2.5, 0, 360)
47
48 - def Q (self):
49 self.O() 50 self._draw_line (2.6, 3, 4, 5)
51
52 - def C (self):
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
58 - def G (self):
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
66 - def W (self):
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
72 - def V (self):
73 self._draw_line (0, 0, 2, 5) 74 self._draw_line (4, 0, 2, 5)
75
76 - def T (self):
77 self._draw_rect (0, 0, 5, 1) 78 self._draw_rect (2, 1, 3, 5)
79
80 - def I (self):
81 self._draw_rect (2, 0, 3, 5)
82
83 - def A (self):
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
88 - def F (self):
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
93 - def E (self):
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
99 - def BackE (self):
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
105 - def UpE (self):
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
111 - def DownE (self):
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
117 - def H (self):
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
122 - def K (self):
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
127 - def L (self):
128 self._draw_rect (0, 0, 1, 5) 129 self._draw_rect (1, 4, 5, 5)
130
131 - def Z (self):
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
136 - def X (self):
137 self._draw_line (4, 0, 0, 5) 138 self._draw_line (0, 0, 4, 5)
139
140 - def NM (self):
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
146 - def N (self):
147 self.NM () 148 self._draw_line (0, 0, 4, 5)
149
150 - def BackN (self):
151 self.NM () 152 self._draw_line (4, 0, 0, 5)
153
154 - def M (self):
155 self.NM () 156 self._draw_line (0, 0, 2, 5) 157 self._draw_line (4, 0, 2, 5)
158
159 - def gamma (self):
160 self._draw_rect (0, 0, 5, 1) 161 self._draw_rect (0, 0, 1, 5)
162
163 - def delta (self):
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
168 - def pi (self):
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
173 - def cross (self):
174 self._draw_rect (2, 0, 3, 5) 175 self._draw_rect (0, 2, 5, 3)
176
177 - def box (self):
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
183 - def star (self):
184 """ 185 Star of 5 points 186 """ 187 n = 5 # can change here 188 list = [] 189 for i in xrange (0, n): 190 theta = (i+0.00001)/n*2*math.pi # points on a circle inside the 5x5 grid 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)) # add point to list 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 # width/Y is screen size (X/Y in cm) 234 #wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X) 235 # screensizes = {_("14 inch"):(28, 21), _("16 inch"):(30, 23)} 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] # in metres 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 # wx.EVT_WINDOW_CREATE (self, self.OnCreate) 253 254 self.ShowFullScreen(1)
255 #--------------------------------- 256 # event handlers 257 #--------------------------------- 258 # def OnCreate(self, event): 259 # self.ShowFullScreen(1) 260 #---------------------------------
261 - def OnPaint (self, event):
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 # _log.info('I think the screen size is %d x %d' % (self.screen_width_pixel, self.screen_height_pixel)) 267 268 # self.setup_DC() 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 # self.draw () 275 # clear the screen 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 # draw size 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
289 - def OnKeyUp (self, key):
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
297 - def OnLeftDown (self, key):
298 if self.distance > 0: 299 self.set_distance (self.distance-1) 300 self.Refresh ()
301
302 - def OnRightDown (self, key):
303 if self.distance < len(self.standard_patient_chart_distances)-1: 304 self.set_distance (self.distance+1) 305 self.Refresh()
306
307 - def OnDClick (self, key):
308 self.Destroy()
309
310 - def OnClose (self, event):
311 self.Destroy()
312
313 - def DestroyWhenApp (self):
314 import sys 315 sys.exit (0)
316 317 #--------------------------------- 318 # internal API 319 #---------------------------------
320 - def _swap_fg_bg_col(self):
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 #---------------------------------
327 - def _draw_rect(self, x1, y1, x2, y2):
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 # now do wedge as background, to give arc pen-stroke 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 #---------------------------------
378 - def _draw_line(self, x1, y1, x2, y2, width = 1):
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 #---------------------------------
393 - def set_distance (self, n):
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 # Snellen characters are the smallest readable characters by 400 # an average person at the stated distance. They are defined 401 # exactly as being in a box which subtends 5' of an arc on the 402 # patient's eye, each stroke subtending 1' of arc 403 one_minute = (math.pi / 180) / 60 404 blocksize = (self.standard_patient_chart_distances[n] * 100) * math.atan(one_minute) # in cm 405 # convert to pixels 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 # how many characters can we fit now? 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 # def draw (self): 429 # """ 430 # displays characters in the centre of the screen from the 431 # selected alphabet 432 # """ 433 # # clear the screen 434 # self._swap_fg_bg_col() 435 # self.dc.DrawRectangle (0,0, self.screen_width_pixel, self.screen_height_pixel) 436 # self._swap_fg_bg_col () 437 # # draw size 438 # self.dc.DrawText (str(self.standard_patient_chart_distances[self.distance]), 20, 20) 439 # self.startX = self.spacing 440 # for i in self.choices: 441 # i (self) 442 # self.startX += self.blockX*5 443 # self.startX += self.spacing 444 445 # def setup_DC (self): 446 # self.dc.SetFont (wx.Font (36, wx.ROMAN, wx.NORMAL, wx.NORMAL)) 447 # self.dc.SetBrush (wx.BLACK_BRUSH) 448 # self.dc.SetBackground (wx.WHITE_BRUSH) 449 # self.dc.SetPen (wx.TRANSPARENT_PEN) 450 451 #============================================================================
452 -class cSnellenCfgDlg (wx.Dialog):
453 """ 454 Dialogue class to get Snellen chart settings. 455 """
456 - def __init__ (self):
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 # print wx.DisplaySize() 467 # print wx.DisplaySizeMM() 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 # self.Show(1) 507 # self.parent = parent 508
509 - def OnClose (self, event):
510 self.EndModal (wx.ID_CANCEL)
511
512 - def OnCancel (self, event):
513 self.EndModal (wx.ID_CANCEL)
514
515 - def OnOK (self, event):
516 # if self.Validate() and self.TransferDataFromWindow(): 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 # self.EndModal(1) 529 530 #============================================================================ 531 # main 532 #---------------------------------------------------------------------------- 533 if __name__ == '__main__': 534 535 gmI18N.activate_locale() 536 gmI18N.install_domain('gnumed') 537
538 - class TestApp (wx.App):
539 - def OnInit (self):
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
555 - def main ():
556 app = TestApp () 557 app.MainLoop ()
558 559 main() 560 #============================================================================ 561