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