Package pyvision :: Package gui :: Module EyePicker
[hide private]
[frames] | no frames]

Source Code for Module pyvision.gui.EyePicker

  1  #!/usr/bin/env python 
  2  # PyVision License 
  3  # 
  4  # Copyright (c) 2006-2008 David S. Bolme 
  5  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions 
  9  # are met: 
 10  #  
 11  # 1. Redistributions of source code must retain the above copyright 
 12  # notice, this list of conditions and the following disclaimer. 
 13  #  
 14  # 2. Redistributions in binary form must reproduce the above copyright 
 15  # notice, this list of conditions and the following disclaimer in the 
 16  # documentation and/or other materials provided with the distribution. 
 17  #  
 18  # 3. Neither name of copyright holders nor the names of its contributors 
 19  # may be used to endorse or promote products derived from this software 
 20  # without specific prior written permission. 
 21  #  
 22  #  
 23  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 24  # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 25  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
 26  # A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR 
 27  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 28  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 29  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 30  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 31  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 32  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 33  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 34   
 35  import wx 
 36  import os 
 37  import os.path 
 38  import csv 
 39  import random 
 40  ''' 
 41  This program is a simple gui for selecting eye coordinates for a  
 42  set of images. 
 43   
 44  The program takes a directory as an argument. 
 45   
 46  It loads each image in that directory into a list frame. 
 47   
 48  It displays the image in the frame.  
 49   
 50  The image is clicked on to select the eye coordinates. 
 51   
 52  Eye coordinates are saved to a file. 
 53  ''' 
 54   
 55  IMAGE_FORMATS=[".JPG",".PNG",".PPM",".PGM",".GIF",".TIF",".TIFF",] 
 56   
57 -class EyePickerFrame(wx.Frame):
58
59 - def __init__(self,parent,id,name,image_dir,n_points=None,randomize=False,scale=1.0):
60 wx.Frame.__init__(self,parent,id,name) 61 62 # ---------------- Basic Data ------------------- 63 self.image_dir = image_dir 64 self.n_points = n_points 65 self.image_names = [] 66 self.current_image = None 67 self.image_name = None 68 self.scale = scale 69 for name in os.listdir(image_dir): 70 for format in IMAGE_FORMATS: 71 if name.upper().endswith(format): 72 self.image_names.append(name) 73 if randomize: 74 random.shuffle(self.image_names) 75 self.filename = None 76 self.coords = {} 77 78 # ------------- Other Components ---------------- 79 self.CreateStatusBar() 80 81 # ------------------- Menu ---------------------- 82 83 filemenu= wx.Menu() 84 id_about = wx.NewId() 85 id_open = wx.NewId() 86 id_save = wx.NewId() 87 id_save_as = wx.NewId() 88 id_exit = wx.NewId() 89 # File Menu 90 filemenu.Append(wx.ID_ABOUT, wx.EmptyString) 91 filemenu.AppendSeparator() 92 filemenu.Append(wx.ID_OPEN, wx.EmptyString) 93 filemenu.Append(wx.ID_SAVE, wx.EmptyString) 94 filemenu.Append(wx.ID_SAVEAS, wx.EmptyString) 95 filemenu.AppendSeparator() 96 filemenu.Append(wx.ID_EXIT,wx.EmptyString) 97 # Creating the menubar. 98 menuBar = wx.MenuBar() 99 menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar 100 self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content. 101 102 103 # ----------------- Image List ------------------ 104 self.list=wx.ListBox(self,wx.NewId(),style=wx.LC_REPORT|wx.SUNKEN_BORDER,choices=self.image_names) 105 self.list.Show(True) 106 107 # --------------- Image Display ----------------- 108 self.static_bitmap = wx.StaticBitmap(self,wx.NewId(), bitmap=wx.EmptyBitmap(300, 300)) 109 self.static_bitmap.SetCursor(wx.CROSS_CURSOR) 110 111 # --------------- Window Layout ----------------- 112 box = wx.BoxSizer(wx.HORIZONTAL) 113 box.Add(self.list, 1, wx.EXPAND) 114 box.Add(self.static_bitmap, 3, wx.EXPAND) 115 116 self.SetAutoLayout(True) 117 self.SetSizer(box) 118 self.Layout() 119 120 # -------------- Event Handleing ---------------- 121 wx.EVT_LISTBOX(self, self.list.GetId(), self.onSelect) 122 wx.EVT_SIZE(self.static_bitmap, self.onBitmapResize) 123 wx.EVT_LEFT_DOWN(self.static_bitmap, self.onClick) 124 wx.EVT_LEFT_UP(self.static_bitmap, self.onRelease) 125 wx.EVT_MOTION(self.static_bitmap, self.onMotion) 126 self.moving = None 127 128 wx.EVT_MENU(self,wx.ID_OPEN,self.onOpen) 129 wx.EVT_MENU(self,wx.ID_SAVE,self.onSave) 130 wx.EVT_MENU(self,wx.ID_SAVEAS,self.onSaveAs) 131 132 wx.EVT_MENU(self,wx.ID_ABOUT,self.onAbout) 133 #wx.EVT_MENU(self,wx.ID_EXIT,self.onExit) 134 135 wx.EVT_CLOSE(self,self.onClose)
136 137
138 - def openCSVFile(self,path):
139 140 reader = csv.reader(open(path, "rb")) 141 first = True 142 eyes = False 143 coords = {} 144 for row in reader: 145 filename = row[0] 146 row = row[1:] 147 148 if len(row)%2 != 0: 149 print "Error Loading File" 150 raise TypeError("Odd number of values in this row") 151 152 points = [] 153 for i in range(0,len(row),2): 154 point = (float(row[i]),float(row[i+1])) 155 points.append(point) 156 157 coords[filename] = points 158 159 160 161 162 163 print "CSV File Data: ", coords 164 165 self.coords = coords
166
167 - def save(self,path):
168 ''' Save the coords to a csv file. ''' 169 writer = csv.writer(open(path,'wb')) 170 171 keys = self.coords.keys() 172 keys.sort() 173 for key in keys: 174 row = [key] 175 for point in self.coords[key]: 176 row.append(point[0]) 177 row.append(point[1]) 178 writer.writerow(row)
179 180
181 - def onSelect(self,event):
182 if self.image_name: 183 if self.n_points != None and len(self.coords[self.image_name]) != self.n_points: 184 print "ERROR: incorrect number of points." 185 186 self.image_name = event.GetString() 187 188 if not self.coords.has_key(self.image_name): 189 self.coords[self.image_name] = [] 190 191 filename = os.path.join(self.image_dir,self.image_name) 192 self.current_image = wx.Image(filename) 193 self.first_click = True 194 self.DisplayImage()
195 196
197 - def onBitmapResize(self,event):
198 w = event.GetSize().GetWidth() 199 h = event.GetSize().GetHeight() 200 201 self.static_bitmap.SetSize(event.GetSize()) 202 203 self.DisplayImage()
204 205
206 - def DisplayImage(self):
207 if self.current_image: 208 tw = self.static_bitmap.GetSize().GetWidth() 209 th = self.static_bitmap.GetSize().GetHeight() 210 sw = self.current_image.GetSize().GetWidth() 211 sh = self.current_image.GetSize().GetHeight() 212 213 #self.scale = min(tw/float(sw),th/float(sh)) 214 215 216 tw = int(sw*self.scale) 217 th = int(sh*self.scale) 218 219 im = self.current_image.Copy() 220 im.Rescale(tw,th) 221 bm = im.ConvertToBitmap() 222 bmdc = wx.MemoryDC(bm) 223 bmdc.SetBrush(wx.TRANSPARENT_BRUSH) 224 bmdc.SetPen(wx.RED_PEN) 225 bmdc.SetTextForeground(wx.RED) 226 227 i = 1 228 for point in self.coords[self.image_name]: 229 bmdc.DrawCircle(self.scale*point[0], self.scale*point[1], 5) 230 w,h = bmdc.GetTextExtent(str(i)) 231 bmdc.DrawText(str(i),self.scale*point[0]-w/2, self.scale*point[1]+5) 232 i += 1 233 234 del bmdc 235 236 self.static_bitmap.SetBitmap(bm)
237 238 239 # ------------- Event Handlers ---------------
240 - def onClick(self,event):
241 x = event.GetX()/self.scale 242 y = event.GetY()/self.scale 243 244 if not self.image_name: return 245 246 # Adjust a point 247 for i in range(len(self.coords[self.image_name])): 248 px,py = self.coords[self.image_name][i] 249 if abs(px - x) < 4 and abs(py - y) < 4: 250 self.coords[self.image_name][i] = (x,y,) 251 self.moving = i 252 self.DisplayImage() 253 return 254 255 # Allow the user to enter new points if the image was just loaded. 256 if self.first_click: 257 self.coords[self.image_name] = [] 258 self.first_click = False 259 260 if len(self.coords[self.image_name]) < self.n_points or self.n_points == None: 261 self.coords[self.image_name].append((x,y,)) 262 self.moving = len(self.coords[self.image_name]) - 1 263 self.DisplayImage()
264
265 - def onMotion(self,event):
266 x = event.GetX()/self.scale 267 y = event.GetY()/self.scale 268 269 if self.moving != None: 270 self.coords[self.image_name][self.moving] = (x,y,) 271 self.DisplayImage()
272 273
274 - def onRelease(self,event):
275 x = event.GetX()/self.scale 276 y = event.GetY()/self.scale 277 278 if self.moving != None: 279 self.coords[self.image_name][self.moving] = (x,y,) 280 self.DisplayImage() 281 282 self.moving = None
283
284 - def onAbout(self,event):
285 dlg = wx.MessageDialog(self,message="For more information visit:\n\nhttp://pyvision.sourceforge.net",style = wx.OK ) 286 result = dlg.ShowModal()
287 288
289 - def onOpen(self,event):
290 print "Open" 291 fd = wx.FileDialog(self,style=wx.FD_OPEN) 292 fd.ShowModal() 293 self.filename = fd.GetPath() 294 print "On Open...",self.filename 295 296 self.openCSVFile(self.filename)
297 298
299 - def onSave(self,event):
300 if self.filename == None: 301 # In this case perform a "Save As" 302 self.onSaveAs(event) 303 else: 304 self.save(self.filename)
305
306 - def onSaveAs(self,event):
307 fd = wx.FileDialog(self,message="Save the coordinates as...",style=wx.FD_SAVE, 308 wildcard="Comma separated value (*.csv)|*.csv") 309 fd.ShowModal() 310 self.filename = fd.GetPath() 311 312 self.save(self.filename)
313
314 - def onClose(self,event):
315 dlg = wx.MessageDialog(self,message="Would you like to save the coordinates before exiting?",style = wx.YES_NO | wx.YES_DEFAULT) 316 result = dlg.ShowModal() 317 if result == wx.ID_YES: 318 print "Saving..." 319 self.onSave(event) 320 else: 321 print "Discarding changes..." 322 323 # Pass this on to the default handler. 324 event.Skip()
325 326 327 if __name__ == '__main__': 328 app = wx.App(False) 329 330 dir_dialog = wx.DirDialog(None, message = "Please select a directory that contains images.") 331 err = dir_dialog.ShowModal() 332 image_dir = '.' 333 if(err == wx.ID_OK): 334 image_dir = dir_dialog.GetPath() 335 else: 336 print "Error getting path:",err 337 338 print "Image Dir",image_dir 339 scale = 1.0 340 341 frame = EyePickerFrame(None, wx.ID_ANY, "Eye Selector",image_dir,n_points=None,randomize=True,scale=scale) 342 frame.Show(True) 343 app.MainLoop() 344