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