class VideoBuffer( object ): def __init__( self, camera, refTime=None, dirName='.', fps=25, bufferSeconds=4.0, owner=None, burstMode=True ): self.camera = camera self.refTime = refTime if refTime is not None else now() self.dirName = dirName self.fps = fps self.frameMax = int(fps * bufferSeconds) self.frameDelay = 1.0 / fps self.frameDelayTimeDelta = timedelta(seconds=self.frameDelay) self.frameSaver = None self.fcb = FrameCircBuf() self.owner = owner # Destination to send photos after they are taken. self.burstMode = burstMode self.timer = CallbackTimer( self.recordVideoFrame ) self.reset() def setOwner( self, owner=None ): self.owner = owner def reset( self ): if self.frameSaver and self.frameSaver.is_alive(): self.frameSaver.stop() if self.timer.IsRunning(): self.timer.Stop() self.fcb.reset( self.frameMax ) self.frameCount = 0 self.frameSaver = FrameSaver() self.frameSaver.start() self.tFindLast = now() - timedelta( days=1 ) self.lastSampleTime = now() self.sampleCount = 0 def recordVideoFrame( self ): tNow = now() self.sampleCount += 1 if not self.camera: return try: image = self.camera.getImage() self.fcb.append( tNow, image ) if self.owner is not None: wx.PostEvent( self.owner, PhotoEvent(t=tNew, photo=image) ) except Exception as e: logException( e, sys.exc_info() ) def getFrameRate( self ): tNow = now() rate = self.sampleCount / (tNow -self.lastSampleTime).total_seconds() self.lastSampleTime = tNow self.sampleCount = 0 return rate def start( self ): self.reset() milliseconds = int(self.frameDelay * 1000.0) self.timer.Start( milliseconds, oneShot=False ) def stop( self ): if self.timer.IsRunning(): self.timer.Stop() if self.frameSaver and self.frameSaver.is_alive(): self.frameSaver.stop() self.frameSaver = None self.lastSampleTime = now() def takePhoto( self, bib, t ): tNow = now() tFind = self.refTime + timedelta( seconds = t + Model.race.advancePhotoMilliseconds / 1000.0 ) if tFind > tNow: wx.CallLater( int(((tFind - tNow).total_seconds() + 0.1) * 1000.0), self.takePhoto, bib, t ) return # If burst mode, check if there was another rider before within frameDelay seconds. # If so, also save the frame just before the given time. # Always save the closest frame after the given time. times, frames = self.fcb.findBeforeAfter( tFind, 1, # 1 if tFind - self.tFindLast < self.frameDelayTimeDelta and self.burstMode else 0, 1 ) for i, frame in enumerate( frames ): t = (times[i]-self.refTime).total_seconds() self.frameSaver.save( GetFilename(bib, t, self.dirName, i), bib, t, frame ) self.frameCount += len(frames) self.tFindLast = tFind def getT( self, i ): return self.fcb.getT(i) def getFrame( self, i ): return self.fcb.getFrame(i) def getTimeFrame( self, i ): return (self.fcb.getT(i), self.fcb.getFrame(i)) def findBeforeAfter( self, t, before=0, after=1 ): ''' Call the frame cyclic buffer from race time, not clock time. ''' tFind = self.refTime + timedelta( seconds=t ) times, frames = self.fcb.findBeforeAfter( tFind, before, after ) return zip( times, frames ) def __len__( self ): return self.frameMax
class MainWin( wx.Frame ): def __init__( self, parent, id = wx.ID_ANY, title='', size=(1000,800) ): wx.Frame.__init__(self, parent, id, title, size=size) self.fps = 25 self.frameDelay = 1.0 / self.fps self.bufferSecs = 10 self.tFrameCount = self.tLaunch = self.tLast = now() self.frameCount = 0 self.frameCountUpdate = self.fps * 2 self.fpsActual = 0.0 self.fpt = timedelta(seconds=0) self.fcb = FrameCircBuf( self.bufferSecs * self.fps ) self.config = wx.Config(appName="CrossMgrCamera", vendorName="SmartCyclingSolutions", #style=wx.CONFIG_USE_LOCAL_FILE ) self.requestQ = Queue() # Select photos from photobuf. self.writerQ = Queue( 400 ) # Selected photos waiting to be written out. self.ftpQ = Queue() # Photos waiting to be ftp'd. self.renamerQ = Queue() # Photos waiting to be renamed and possibly ftp'd. self.messageQ = Queue() # Collection point for all status/failure messages. self.SetBackgroundColour( wx.Colour(232,232,232) ) mainSizer = wx.BoxSizer( wx.VERTICAL ) self.primaryImage = ScaledImage( self, style=wx.BORDER_SUNKEN, size=(imageWidth, imageHeight) ) self.beforeImage = ScaledImage( self, style=wx.BORDER_SUNKEN, size=(imageWidth, imageHeight) ) self.afterImage = ScaledImage( self, style=wx.BORDER_SUNKEN, size=(imageWidth, imageHeight) ) self.beforeAfterImages = [self.beforeImage, self.afterImage] #------------------------------------------------------------------------------------------------ headerSizer = wx.BoxSizer( wx.HORIZONTAL ) self.logo = Utils.GetPngBitmap('CrossMgrHeader.png') headerSizer.Add( wx.StaticBitmap(self, wx.ID_ANY, self.logo) ) self.title = wx.StaticText(self, label='CrossMgr Camera\nVersion {}'.format(AppVerName.split()[1]), style=wx.ALIGN_RIGHT ) self.title.SetFont( wx.Font( (0,28), wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL ) ) headerSizer.Add( self.title, flag=wx.ALL, border=10 ) #------------------------------------------------------------------------------ self.cameraDeviceLabel = wx.StaticText(self, label='Camera Device:') self.cameraDevice = wx.StaticText( self ) boldFont = self.cameraDevice.GetFont() boldFont.SetWeight( wx.BOLD ) self.cameraDevice.SetFont( boldFont ) self.cameraResolution = wx.StaticText( self ) self.cameraResolution.SetFont( boldFont ) bitmap = wx.Bitmap( clipboard_xpm ) self.copyLogToClipboard = wx.BitmapButton( self, bitmap=bitmap ) self.copyLogToClipboard.Bind( wx.EVT_BUTTON, self.onCopyLogToClipboard ) self.reset = wx.Button( self, label="Reset Camera" ) self.reset.Bind( wx.EVT_BUTTON, self.resetCamera ) cameraDeviceSizer = wx.BoxSizer( wx.HORIZONTAL ) cameraDeviceSizer.Add( self.cameraDeviceLabel, flag=wx.ALIGN_CENTRE_VERTICAL|wx.ALIGN_RIGHT ) cameraDeviceSizer.Add( self.cameraDevice, flag=wx.ALIGN_CENTRE_VERTICAL|wx.LEFT, border=8 ) cameraDeviceSizer.Add( self.cameraResolution, flag=wx.ALIGN_CENTRE_VERTICAL|wx.LEFT, border=8 ) cameraDeviceSizer.Add( self.copyLogToClipboard, flag=wx.ALIGN_CENTRE_VERTICAL|wx.LEFT, border=8 ) cameraDeviceSizer.Add( self.reset, flag=wx.ALIGN_CENTRE_VERTICAL|wx.LEFT, border=24 ) #------------------------------------------------------------------------------ self.targetProcessingTimeLabel = wx.StaticText(self, label='Target Frames:') self.targetProcessingTime = wx.StaticText(self, label=u'{:.3f}'.format(self.fps)) self.targetProcessingTime.SetFont( boldFont ) self.targetProcessingTimeUnit = wx.StaticText(self, label='per sec') self.framesPerSecondLabel = wx.StaticText(self, label='Actual Frames:') self.framesPerSecond = wx.StaticText(self, label='25.000') self.framesPerSecond.SetFont( boldFont ) self.framesPerSecondUnit = wx.StaticText(self, label='per sec') self.availableMsPerFrameLabel = wx.StaticText(self, label='Available Time Per Frame:') self.availableMsPerFrame = wx.StaticText(self, label=u'{:.0f}'.format(1000.0*self.frameDelay)) self.availableMsPerFrame.SetFont( boldFont ) self.availableMsPerFrameUnit = wx.StaticText(self, label='ms') self.frameProcessingTimeLabel = wx.StaticText(self, label='Actual Frame Processing:') self.frameProcessingTime = wx.StaticText(self, label='20') self.frameProcessingTime.SetFont( boldFont ) self.frameProcessingTimeUnit = wx.StaticText(self, label='ms') pfgs = wx.FlexGridSizer( rows=0, cols=6, vgap=4, hgap=8 ) fRight = wx.ALIGN_CENTRE_VERTICAL|wx.ALIGN_RIGHT fLeft = wx.ALIGN_CENTRE_VERTICAL #------------------- Row 1 ------------------------------ pfgs.Add( self.targetProcessingTimeLabel, flag=fRight ) pfgs.Add( self.targetProcessingTime, flag=fRight ) pfgs.Add( self.targetProcessingTimeUnit, flag=fLeft ) pfgs.Add( self.availableMsPerFrameLabel, flag=fRight ) pfgs.Add( self.availableMsPerFrame, flag=fRight ) pfgs.Add( self.availableMsPerFrameUnit, flag=fLeft ) #------------------- Row 2 ------------------------------ pfgs.Add( self.framesPerSecondLabel, flag=fRight ) pfgs.Add( self.framesPerSecond, flag=fRight ) pfgs.Add( self.framesPerSecondUnit, flag=fLeft ) pfgs.Add( self.frameProcessingTimeLabel, flag=fRight ) pfgs.Add( self.frameProcessingTime, flag=fRight ) pfgs.Add( self.frameProcessingTimeUnit, flag=fLeft ) statsSizer = wx.BoxSizer( wx.VERTICAL ) statsSizer.Add( cameraDeviceSizer ) statsSizer.Add( pfgs, flag=wx.TOP, border=8 ) headerSizer.Add( statsSizer, flag=wx.ALL, border=4 ) mainSizer.Add( headerSizer ) self.messagesText = wx.TextCtrl( self, style=wx.TE_READONLY|wx.TE_MULTILINE|wx.HSCROLL, size=(350,imageHeight) ) self.messageManager = MessageManager( self.messagesText ) border = 2 row1Sizer = wx.BoxSizer( wx.HORIZONTAL ) row1Sizer.Add( self.primaryImage, flag=wx.ALL, border=border ) row1Sizer.Add( self.messagesText, 1, flag=wx.TOP|wx.BOTTOM|wx.RIGHT|wx.EXPAND, border=border ) mainSizer.Add( row1Sizer, 1, flag=wx.EXPAND ) row2Sizer = wx.BoxSizer( wx.HORIZONTAL ) row2Sizer.Add( self.beforeImage, flag=wx.LEFT|wx.RIGHT|wx.BOTTOM, border=border ) row2Sizer.Add( self.afterImage, flag=wx.RIGHT|wx.BOTTOM, border=border ) mainSizer.Add( row2Sizer ) #------------------------------------------------------------------------------------------------ # Create a timer to update the frame loop. # self.timer = wx.Timer() self.timer.Bind( wx.EVT_TIMER, self.frameLoop ) self.Bind(wx.EVT_CLOSE, self.onCloseWindow) self.readOptions() self.SetSizerAndFit( mainSizer ) # Start the message reporting thread so we can see what is going on. self.messageThread = threading.Thread( target=self.showMessages ) self.messageThread.daemon = True self.messageThread.start() self.grabFrameOK = False for i in [self.primaryImage, self.beforeImage, self.afterImage]: i.SetTestImage() # Start the frame loop. delayAdjustment = 0.80 if 'win' in sys.platform else 0.98 ms = int(1000 * self.frameDelay * delayAdjustment) self.timer.Start( ms, False ) def Start( self ): self.messageQ.put( ('', '************************************************') ) self.messageQ.put( ('started', now().strftime('%Y/%m/%d %H:%M:%S')) ) self.startThreads() self.startCamera() def onCopyLogToClipboard( self, event ): with open( redirectFileName, 'r' ) as fp: logData = fp.read() dataObj = wx.TextDataObject() dataObj.SetText(logData) if wx.TheClipboard.Open(): wx.TheClipboard.SetData( dataObj ) wx.TheClipboard.Close() Utils.MessageOK(self, u'\n\n'.join( [_("Log file copied to clipboard."), _("You can now paste it into an email.")] ), _("Success") ) else: Utils.MessageOK(self, _("Unable to open the clipboard."), _("Error"), wx.ICON_ERROR ) def showMessages( self ): while 1: message = self.messageQ.get() assert len(message) == 2, 'Incorrect message length' cmd, info = message wx.CallAfter( self.messageManager.write, '{}: {}'.format(cmd, info) if cmd else info ) def startCamera( self ): self.camera = None try: self.camera = Device( max(self.getCameraDeviceNum(), 0) ) except Exception as e: self.messageQ.put( ('camera', 'Error: {}'.format(e)) ) return False try: self.camera.set_resolution( *self.getCameraResolution() ) self.messageQ.put( ('camera', '{}x{} Supported'.format(*self.getCameraResolution())) ) except Exception as e: self.messageQ.put( ('camera', '{}x{} Unsupported Resolution'.format(*self.getCameraResolution())) ) self.messageQ.put( ('camera', 'Successfully Connected: Device: {}'.format(self.getCameraDeviceNum()) ) ) for i in self.beforeAfterImages: i.SetTestImage() return True def startThreads( self ): self.grabFrameOK = False self.listenerThread = SocketListener( self.requestQ, self.messageQ ) error = self.listenerThread.test() if error: wx.MessageBox('Socket Error:\n\n{}\n\nIs another CrossMgrVideo or CrossMgrCamera running on this computer?'.format(error), "Socket Error", wx.OK | wx.ICON_ERROR ) wx.Exit() self.writerThread = threading.Thread( target=PhotoWriter, args=(self.writerQ, self.messageQ, self.ftpQ) ) self.writerThread.daemon = True self.renamerThread = threading.Thread( target=PhotoRenamer, args=(self.renamerQ, self.writerQ, self.messageQ) ) self.renamerThread.daemon = True self.ftpThread = threading.Thread( target=FTPWriter, args=(self.ftpQ, self.messageQ) ) self.ftpThread.daemon = True self.fcb = FrameCircBuf( int(self.bufferSecs * self.fps) ) self.listenerThread.start() self.writerThread.start() self.renamerThread.start() self.ftpThread.start() self.grabFrameOK = True self.messageQ.put( ('threads', 'Successfully Launched') ) return True def frameLoop( self, event=None ): if not self.grabFrameOK: return self.frameCount += 1 tNow = now() # Compute frame rate statistics. if self.frameCount == self.frameCountUpdate: self.fpsActual = self.frameCount / (tNow - self.tFrameCount).total_seconds() self.frameCount = 0 self.tFrameCount = tNow self.framesPerSecond.SetLabel( u'{:.3f}'.format(self.fpsActual) ) self.availableMsPerFrame.SetLabel( u'{:.0f}'.format(1000.0/self.fpsActual) ) self.frameProcessingTime.SetLabel( u'{:.0f}'.format(1000.0*self.fpt.total_seconds()) ) try: image = self.camera.getImage() except Exception as e: self.messageQ.put( ('error', 'Webcam Failure: {}'.format(e) ) ) self.grabFrameOK = False return if not image: return image = AddPhotoHeader( PilImageToWxImage(image), time=tNow, raceSeconds=(tNow - self.tLaunch).total_seconds(), bib=999, firstNameTxt=u'Firstname', lastNameTxt=u'LASTNAME', teamTxt=u'Team', raceNameTxt='Racename' ) self.fcb.append( tNow, image ) wx.CallAfter( self.primaryImage.SetImage, image ) # Process any save messages while 1: try: message = self.requestQ.get(False) except Empty: break tSearch = message['time'] advanceSeconds = message.get('advanceSeconds', 0.0) if advanceSeconds: tSearch += timedelta(seconds=advanceSeconds) times, frames = self.fcb.findBeforeAfter( tSearch, 1, 1 ) if not frames: self.messageQ.put( ('error', 'No photos for {} at {}'.format(message.get('bib', None), message['time'].isoformat()) ) ) lastImage = None for i, f in enumerate(frames): fname = GetPhotoFName( message['dirName'], message.get('bib',None), message.get('raceSeconds',None), i, times[i] ) image = AddPhotoHeader( f, message.get('bib', None), message.get('time', None), message.get('raceSeconds', None), message.get('firstName',u''), message.get('lastName',u''), message.get('team',u''), message.get('raceName',u'') ) #if lastImage is not None and image.GetData() == lastImage.GetData(): # self.messageQ.put( ('duplicate', '"{}"'.format(os.path.basename(fname))) ) # continue wx.CallAfter( self.beforeAfterImages[i].SetImage, image ) SaveImage( fname, image, message.get('ftpInfo', None), self.messageQ, self.writerQ ) lastImage = image if (now() - tNow).total_seconds() > self.frameDelay / 2.0: break self.fpt = now() - tNow self.tLast = tNow def shutdown( self ): # Ensure that all images in the queue are saved. self.grabFrameOK = False if hasattr(self, 'writerThread'): self.writerQ.put( ('terminate', ) ) self.writerThread.join() def resetCamera( self, event ): dlg = ConfigDialog( self, self.getCameraDeviceNum(), self.getCameraResolution() ) ret = dlg.ShowModal() cameraDeviceNum = dlg.GetCameraDeviceNum() cameraResolution = dlg.GetCameraResolution() dlg.Destroy() if ret != wx.ID_OK: return False self.setCameraDeviceNum( cameraDeviceNum ) self.setCameraResolution( *cameraResolution ) self.writeOptions() self.grabFrameOK = self.startCamera() return True def setCameraDeviceNum( self, num ): self.cameraDevice.SetLabel( unicode(num) ) def setCameraResolution( self, width, height ): self.cameraResolution.SetLabel( u'{}x{}'.format(width, height) ) def getCameraDeviceNum( self ): return int(self.cameraDevice.GetLabel()) def getCameraResolution( self ): try: resolution = [int(v) for v in self.cameraResolution.GetLabel().split('x')] return resolution[0], resolution[1] except: return 640, 400 def onCloseWindow( self, event ): self.shutdown() wx.Exit() def writeOptions( self ): self.config.Write( 'CameraDevice', self.cameraDevice.GetLabel() ) self.config.Write( 'CameraResolution', self.cameraResolution.GetLabel() ) self.config.Flush() def readOptions( self ): self.cameraDevice.SetLabel( self.config.Read('CameraDevice', u'0') ) self.cameraResolution.SetLabel( self.config.Read('CameraResolution', u'640x480') )
class MainWin(wx.Frame): def __init__(self, parent, id=wx.ID_ANY, title='', size=(1000, 800)): wx.Frame.__init__(self, parent, id, title, size=size) self.fps = 25 self.frameDelay = 1.0 / self.fps self.bufferSecs = 10 self.tFrameCount = self.tLaunch = self.tLast = now() self.frameCount = 0 self.frameCountUpdate = self.fps * 2 self.fpsActual = 0.0 self.fpt = timedelta(seconds=0) self.fcb = FrameCircBuf(self.bufferSecs * self.fps) self.config = wx.Config( appName="CrossMgrCamera", vendorName="SmartCyclingSolutions", #style=wx.CONFIG_USE_LOCAL_FILE ) self.requestQ = Queue() # Select photos from photobuf. self.writerQ = Queue(400) # Selected photos waiting to be written out. self.ftpQ = Queue() # Photos waiting to be ftp'd. self.renamerQ = Queue( ) # Photos waiting to be renamed and possibly ftp'd. self.messageQ = Queue( ) # Collection point for all status/failure messages. self.SetBackgroundColour(wx.Colour(232, 232, 232)) mainSizer = wx.BoxSizer(wx.VERTICAL) self.primaryImage = ScaledImage(self, style=wx.BORDER_SUNKEN, size=(imageWidth, imageHeight)) self.beforeImage = ScaledImage(self, style=wx.BORDER_SUNKEN, size=(imageWidth, imageHeight)) self.afterImage = ScaledImage(self, style=wx.BORDER_SUNKEN, size=(imageWidth, imageHeight)) self.beforeAfterImages = [self.beforeImage, self.afterImage] #------------------------------------------------------------------------------------------------ headerSizer = wx.BoxSizer(wx.HORIZONTAL) self.logo = Utils.GetPngBitmap('CrossMgrHeader.png') headerSizer.Add(wx.StaticBitmap(self, wx.ID_ANY, self.logo)) self.title = wx.StaticText(self, label='CrossMgr Camera\nVersion {}'.format( AppVerName.split()[1]), style=wx.ALIGN_RIGHT) self.title.SetFont( wx.Font((0, 28), wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) headerSizer.Add(self.title, flag=wx.ALL, border=10) #------------------------------------------------------------------------------ self.cameraDeviceLabel = wx.StaticText(self, label='Camera Device:') self.cameraDevice = wx.StaticText(self) boldFont = self.cameraDevice.GetFont() boldFont.SetWeight(wx.BOLD) self.cameraDevice.SetFont(boldFont) self.cameraResolution = wx.StaticText(self) self.cameraResolution.SetFont(boldFont) bitmap = wx.Bitmap(clipboard_xpm) self.copyLogToClipboard = wx.BitmapButton(self, bitmap=bitmap) self.copyLogToClipboard.Bind(wx.EVT_BUTTON, self.onCopyLogToClipboard) self.reset = wx.Button(self, label="Reset Camera") self.reset.Bind(wx.EVT_BUTTON, self.resetCamera) cameraDeviceSizer = wx.BoxSizer(wx.HORIZONTAL) cameraDeviceSizer.Add(self.cameraDeviceLabel, flag=wx.ALIGN_CENTRE_VERTICAL | wx.ALIGN_RIGHT) cameraDeviceSizer.Add(self.cameraDevice, flag=wx.ALIGN_CENTRE_VERTICAL | wx.LEFT, border=8) cameraDeviceSizer.Add(self.cameraResolution, flag=wx.ALIGN_CENTRE_VERTICAL | wx.LEFT, border=8) cameraDeviceSizer.Add(self.copyLogToClipboard, flag=wx.ALIGN_CENTRE_VERTICAL | wx.LEFT, border=8) cameraDeviceSizer.Add(self.reset, flag=wx.ALIGN_CENTRE_VERTICAL | wx.LEFT, border=24) #------------------------------------------------------------------------------ self.targetProcessingTimeLabel = wx.StaticText(self, label='Target Frames:') self.targetProcessingTime = wx.StaticText(self, label=u'{:.3f}'.format( self.fps)) self.targetProcessingTime.SetFont(boldFont) self.targetProcessingTimeUnit = wx.StaticText(self, label='per sec') self.framesPerSecondLabel = wx.StaticText(self, label='Actual Frames:') self.framesPerSecond = wx.StaticText(self, label='25.000') self.framesPerSecond.SetFont(boldFont) self.framesPerSecondUnit = wx.StaticText(self, label='per sec') self.availableMsPerFrameLabel = wx.StaticText( self, label='Available Time Per Frame:') self.availableMsPerFrame = wx.StaticText(self, label=u'{:.0f}'.format( 1000.0 * self.frameDelay)) self.availableMsPerFrame.SetFont(boldFont) self.availableMsPerFrameUnit = wx.StaticText(self, label='ms') self.frameProcessingTimeLabel = wx.StaticText( self, label='Actual Frame Processing:') self.frameProcessingTime = wx.StaticText(self, label='20') self.frameProcessingTime.SetFont(boldFont) self.frameProcessingTimeUnit = wx.StaticText(self, label='ms') pfgs = wx.FlexGridSizer(rows=0, cols=6, vgap=4, hgap=8) fRight = wx.ALIGN_CENTRE_VERTICAL | wx.ALIGN_RIGHT fLeft = wx.ALIGN_CENTRE_VERTICAL #------------------- Row 1 ------------------------------ pfgs.Add(self.targetProcessingTimeLabel, flag=fRight) pfgs.Add(self.targetProcessingTime, flag=fRight) pfgs.Add(self.targetProcessingTimeUnit, flag=fLeft) pfgs.Add(self.availableMsPerFrameLabel, flag=fRight) pfgs.Add(self.availableMsPerFrame, flag=fRight) pfgs.Add(self.availableMsPerFrameUnit, flag=fLeft) #------------------- Row 2 ------------------------------ pfgs.Add(self.framesPerSecondLabel, flag=fRight) pfgs.Add(self.framesPerSecond, flag=fRight) pfgs.Add(self.framesPerSecondUnit, flag=fLeft) pfgs.Add(self.frameProcessingTimeLabel, flag=fRight) pfgs.Add(self.frameProcessingTime, flag=fRight) pfgs.Add(self.frameProcessingTimeUnit, flag=fLeft) statsSizer = wx.BoxSizer(wx.VERTICAL) statsSizer.Add(cameraDeviceSizer) statsSizer.Add(pfgs, flag=wx.TOP, border=8) headerSizer.Add(statsSizer, flag=wx.ALL, border=4) mainSizer.Add(headerSizer) self.messagesText = wx.TextCtrl(self, style=wx.TE_READONLY | wx.TE_MULTILINE | wx.HSCROLL, size=(350, imageHeight)) self.messageManager = MessageManager(self.messagesText) border = 2 row1Sizer = wx.BoxSizer(wx.HORIZONTAL) row1Sizer.Add(self.primaryImage, flag=wx.ALL, border=border) row1Sizer.Add(self.messagesText, 1, flag=wx.TOP | wx.BOTTOM | wx.RIGHT | wx.EXPAND, border=border) mainSizer.Add(row1Sizer, 1, flag=wx.EXPAND) row2Sizer = wx.BoxSizer(wx.HORIZONTAL) row2Sizer.Add(self.beforeImage, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM, border=border) row2Sizer.Add(self.afterImage, flag=wx.RIGHT | wx.BOTTOM, border=border) mainSizer.Add(row2Sizer) #------------------------------------------------------------------------------------------------ # Create a timer to update the frame loop. # self.timer = wx.Timer() self.timer.Bind(wx.EVT_TIMER, self.frameLoop) self.Bind(wx.EVT_CLOSE, self.onCloseWindow) self.readOptions() self.SetSizerAndFit(mainSizer) # Start the message reporting thread so we can see what is going on. self.messageThread = threading.Thread(target=self.showMessages) self.messageThread.daemon = True self.messageThread.start() self.grabFrameOK = False for i in [self.primaryImage, self.beforeImage, self.afterImage]: i.SetTestImage() # Start the frame loop. delayAdjustment = 0.80 if 'win' in sys.platform else 0.98 ms = int(1000 * self.frameDelay * delayAdjustment) self.timer.Start(ms, False) def Start(self): self.messageQ.put( ('', '************************************************')) self.messageQ.put(('started', now().strftime('%Y/%m/%d %H:%M:%S'))) self.startThreads() self.startCamera() def onCopyLogToClipboard(self, event): with open(redirectFileName, 'r') as fp: logData = fp.read() dataObj = wx.TextDataObject() dataObj.SetText(logData) if wx.TheClipboard.Open(): wx.TheClipboard.SetData(dataObj) wx.TheClipboard.Close() Utils.MessageOK( self, u'\n\n'.join([ _("Log file copied to clipboard."), _("You can now paste it into an email.") ]), _("Success")) else: Utils.MessageOK(self, _("Unable to open the clipboard."), _("Error"), wx.ICON_ERROR) def showMessages(self): while 1: message = self.messageQ.get() assert len(message) == 2, 'Incorrect message length' cmd, info = message wx.CallAfter(self.messageManager.write, '{}: {}'.format(cmd, info) if cmd else info) def startCamera(self): self.camera = None try: self.camera = Device(max(self.getCameraDeviceNum(), 0)) except Exception as e: self.messageQ.put(('camera', 'Error: {}'.format(e))) return False try: self.camera.set_resolution(*self.getCameraResolution()) self.messageQ.put( ('camera', '{}x{} Supported'.format(*self.getCameraResolution()))) except Exception as e: self.messageQ.put(('camera', '{}x{} Unsupported Resolution'.format( *self.getCameraResolution()))) self.messageQ.put( ('camera', 'Successfully Connected: Device: {}'.format( self.getCameraDeviceNum()))) for i in self.beforeAfterImages: i.SetTestImage() return True def startThreads(self): self.grabFrameOK = False self.listenerThread = SocketListener(self.requestQ, self.messageQ) error = self.listenerThread.test() if error: wx.MessageBox( 'Socket Error:\n\n{}\n\nIs another CrossMgrVideo or CrossMgrCamera running on this computer?' .format(error), "Socket Error", wx.OK | wx.ICON_ERROR) wx.Exit() self.writerThread = threading.Thread(target=PhotoWriter, args=(self.writerQ, self.messageQ, self.ftpQ)) self.writerThread.daemon = True self.renamerThread = threading.Thread(target=PhotoRenamer, args=(self.renamerQ, self.writerQ, self.messageQ)) self.renamerThread.daemon = True self.ftpThread = threading.Thread(target=FTPWriter, args=(self.ftpQ, self.messageQ)) self.ftpThread.daemon = True self.fcb = FrameCircBuf(int(self.bufferSecs * self.fps)) self.listenerThread.start() self.writerThread.start() self.renamerThread.start() self.ftpThread.start() self.grabFrameOK = True self.messageQ.put(('threads', 'Successfully Launched')) return True def frameLoop(self, event=None): if not self.grabFrameOK: return self.frameCount += 1 tNow = now() # Compute frame rate statistics. if self.frameCount == self.frameCountUpdate: self.fpsActual = self.frameCount / ( tNow - self.tFrameCount).total_seconds() self.frameCount = 0 self.tFrameCount = tNow self.framesPerSecond.SetLabel(u'{:.3f}'.format(self.fpsActual)) self.availableMsPerFrame.SetLabel(u'{:.0f}'.format(1000.0 / self.fpsActual)) self.frameProcessingTime.SetLabel(u'{:.0f}'.format( 1000.0 * self.fpt.total_seconds())) try: image = self.camera.getImage() except Exception as e: self.messageQ.put(('error', 'Webcam Failure: {}'.format(e))) self.grabFrameOK = False return if not image: return image = AddPhotoHeader(PilImageToWxImage(image), time=tNow, raceSeconds=(tNow - self.tLaunch).total_seconds(), bib=999, firstNameTxt=u'Firstname', lastNameTxt=u'LASTNAME', teamTxt=u'Team', raceNameTxt='Racename') self.fcb.append(tNow, image) wx.CallAfter(self.primaryImage.SetImage, image) # Process any save messages while 1: try: message = self.requestQ.get(False) except Empty: break tSearch = message['time'] advanceSeconds = message.get('advanceSeconds', 0.0) if advanceSeconds: tSearch += timedelta(seconds=advanceSeconds) times, frames = self.fcb.findBeforeAfter(tSearch, 1, 1) if not frames: self.messageQ.put(('error', 'No photos for {} at {}'.format( message.get('bib', None), message['time'].isoformat()))) lastImage = None for i, f in enumerate(frames): fname = GetPhotoFName(message['dirName'], message.get('bib', None), message.get('raceSeconds', None), i, times[i]) image = AddPhotoHeader(f, message.get('bib', None), message.get('time', None), message.get('raceSeconds', None), message.get('firstName', u''), message.get('lastName', u''), message.get('team', u''), message.get('raceName', u'')) #if lastImage is not None and image.GetData() == lastImage.GetData(): # self.messageQ.put( ('duplicate', '"{}"'.format(os.path.basename(fname))) ) # continue wx.CallAfter(self.beforeAfterImages[i].SetImage, image) SaveImage(fname, image, message.get('ftpInfo', None), self.messageQ, self.writerQ) lastImage = image if (now() - tNow).total_seconds() > self.frameDelay / 2.0: break self.fpt = now() - tNow self.tLast = tNow def shutdown(self): # Ensure that all images in the queue are saved. self.grabFrameOK = False if hasattr(self, 'writerThread'): self.writerQ.put(('terminate', )) self.writerThread.join() def resetCamera(self, event): dlg = ConfigDialog(self, self.getCameraDeviceNum(), self.getCameraResolution()) ret = dlg.ShowModal() cameraDeviceNum = dlg.GetCameraDeviceNum() cameraResolution = dlg.GetCameraResolution() dlg.Destroy() if ret != wx.ID_OK: return False self.setCameraDeviceNum(cameraDeviceNum) self.setCameraResolution(*cameraResolution) self.writeOptions() self.grabFrameOK = self.startCamera() return True def setCameraDeviceNum(self, num): self.cameraDevice.SetLabel(unicode(num)) def setCameraResolution(self, width, height): self.cameraResolution.SetLabel(u'{}x{}'.format(width, height)) def getCameraDeviceNum(self): return int(self.cameraDevice.GetLabel()) def getCameraResolution(self): try: resolution = [ int(v) for v in self.cameraResolution.GetLabel().split('x') ] return resolution[0], resolution[1] except: return 640, 400 def onCloseWindow(self, event): self.shutdown() wx.Exit() def writeOptions(self): self.config.Write('CameraDevice', self.cameraDevice.GetLabel()) self.config.Write('CameraResolution', self.cameraResolution.GetLabel()) self.config.Flush() def readOptions(self): self.cameraDevice.SetLabel(self.config.Read('CameraDevice', u'0')) self.cameraResolution.SetLabel( self.config.Read('CameraResolution', u'640x480'))
class VideoBuffer( object ): def __init__( self, camera, refTime=None, dirName='.', fps=25, bufferSeconds=4.0, owner=None, burstMode=True ): self.camera = camera self.refTime = refTime if refTime is not None else now() self.dirName = dirName self.fps = fps self.frameMax = int(fps * bufferSeconds) self.frameDelay = 1.0 / fps self.frameDelayTimeDelta = timedelta(seconds=self.frameDelay) self.frameSaver = None self.fcb = FrameCircBuf() self.owner = owner # Destination to send photos after they are taken. self.burstMode = burstMode self.timer = CallbackTimer( self.recordVideoFrame ) self.reset() def setOwner( self, owner=None ): self.owner = owner def reset( self ): if self.frameSaver and self.frameSaver.is_alive(): self.frameSaver.stop() if self.timer.IsRunning(): self.timer.Stop() self.fcb.reset( self.frameMax ) self.frameCount = 0 self.frameSaver = FrameSaver() self.frameSaver.start() self.tFindLast = now() - timedelta( days=1 ) self.lastSampleTime = now() self.sampleCount = 0 def recordVideoFrame( self ): tNow = now() self.sampleCount += 1 if not self.camera: return try: image = self.camera.getImage() self.fcb.append( tNow, image ) if self.owner is not None: wx.PostEvent( self.owner, PhotoEvent(t=tNow, photo=image) ) except Exception as e: logException( e, sys.exc_info() ) def getFrameRate( self ): tNow = now() rate = self.sampleCount / (tNow -self.lastSampleTime).total_seconds() self.lastSampleTime = tNow self.sampleCount = 0 return rate def start( self ): self.reset() milliseconds = int(self.frameDelay * 1000.0) self.timer.Start( milliseconds, oneShot=False ) def stop( self ): if self.timer.IsRunning(): self.timer.Stop() if self.frameSaver and self.frameSaver.is_alive(): self.frameSaver.stop() self.frameSaver = None self.lastSampleTime = now() def takePhoto( self, bib, t ): tNow = now() tFind = self.refTime + timedelta( seconds = t + Model.race.advancePhotoMilliseconds / 1000.0 ) if tFind > tNow: wx.CallLater( max(1,int(((tFind - tNow).total_seconds() + 0.1) * 1000.0)), self.takePhoto, bib, t ) return # If burst mode, check if there was another rider before within frameDelay seconds. # If so, also save the frame just before the given time. # Always save the closest frame after the given time. times, frames = self.fcb.findBeforeAfter( tFind, 1, # 1 if tFind - self.tFindLast < self.frameDelayTimeDelta and self.burstMode else 0, 1 ) for i, frame in enumerate( frames ): t = (times[i]-self.refTime).total_seconds() self.frameSaver.save( GetFilename(bib, t, self.dirName, i), bib, t, frame ) self.frameCount += len(frames) self.tFindLast = tFind def getT( self, i ): return self.fcb.getT(i) def getFrame( self, i ): return self.fcb.getFrame(i) def getTimeFrame( self, i ): return (self.fcb.getT(i), self.fcb.getFrame(i)) def findBeforeAfter( self, t, before=0, after=1 ): ''' Call the frame cyclic buffer from race time, not clock time. ''' tFind = self.refTime + timedelta( seconds=t ) times, frames = self.fcb.findBeforeAfter( tFind, before, after ) return zip( times, frames ) def __len__( self ): return self.frameMax
class VideoBuffer( threading.Thread ): def __init__( self, camera, refTime = None, dirName = '.', fps = 25, bufferSeconds = 4.0 ): threading.Thread.__init__( self ) self.daemon = True self.name = 'VideoBuffer' self.camera = camera self.refTime = refTime if refTime is not None else now() self.dirName = dirName self.fps = fps self.frameMax = int(fps * bufferSeconds) self.frameDelay = 1.0 / fps self.frameSaver = None self.fcb = FrameCircBuf() self.reset() def reset( self ): if self.frameSaver and self.frameSaver.is_alive(): self.frameSaver.stop() self.fcb.reset( self.frameMax ) self.frameCount = 0 self.frameSaver = FrameSaver() self.frameSaver.start() self.queue = Queue() def run( self ): self.reset() keepGoing = True while keepGoing: #sys.stderr.write( '.' ) tNow = now() try: self.fcb.append( tNow, self.camera.getImage() ) except Exception as e: logException( e, sys.exc_info() ) break while 1: try: message = self.queue.get( False ) except Empty: break if message[0] == 'Save': cmd, bib, t = message tFind = self.refTime + timedelta( seconds = t + getattr(Model.race, 'advancePhotoMilliseconds', Model.Race.advancePhotoMillisecondsDefault) / 1000.0 ) if tFind > tNow: threading.Timer( (tFind - tNow).total_seconds() + 0.1, self.takePhoto, args=[bib, t] ).start() continue times, frames = self.fcb.findBeforeAfter( tFind, 0, 1 ) for i, frame in enumerate( frames ): t = (times[i]-self.refTime).total_seconds() self.frameSaver.save( GetFilename(bib, t, self.dirName, i), bib, t, frame ) self.frameCount += len(frames) self.queue.task_done() elif message[0] == 'Terminate': self.queue.task_done() self.reset() keepGoing = False break # Sleep until we need to grab the next frame. frameWait = self.frameDelay - (now() - tNow).total_seconds() if frameWait < 0.0: frameWait = self.frameDelay # Give some more time if we are falling behind. time.sleep( frameWait ) def stop( self ): self.queue.put( ['Terminate'] ) self.join() if self.frameSaver.is_alive(): self.frameSaver.stop() self.frameSaver = None def takePhoto( self, bib, t ): self.queue.put( ['Save', bib, t] ) def getT( self, i ): return self.fcb.getT(i) def getFrame( self, i ): return self.fcb.getFrame(i) def getTimeFrame( self, i ): return (self.fcb.getT(i), self.fcb.getFrame(i)) def findBeforeAfter( self, t, before = 0, after = 1 ): ''' Call the frame cyclic buffer from race time, not clock time. ''' tFind = self.refTime + timedelta( seconds = t ) times, frames = self.fcb.findBeforeAfter( tFind, before, after ) return zip( times, frames ) def __len__( self ): return self.frameMax
class MainWin(wx.Frame): def __init__(self, parent, id=wx.ID_ANY, title="", size=(1000, 800)): wx.Frame.__init__(self, parent, id, title, size=size) self.fps = 25 self.frameDelay = 1.0 / self.fps self.bufferSecs = 10 self.tFrameCount = self.tLaunch = self.tLast = now() self.frameCount = 0 self.frameCountUpdate = self.fps * 2 self.fpsActual = 0.0 self.fpt = timedelta(seconds=0) self.fcb = FrameCircBuf(self.bufferSecs * self.fps) self.config = wx.Config( appName="CrossMgrCamera", vendorName="SmartCyclingSolutions", style=wx.CONFIG_USE_LOCAL_FILE ) self.requestQ = Queue() # Select photos from photobuf. self.writerQ = Queue(400) # Selected photos waiting to be written out. self.ftpQ = Queue() # Photos waiting to be ftp'd. self.renamerQ = Queue() # Photos waiting to be renamed and possibly ftp'd. self.messageQ = Queue() # Collection point for all status/failure messages. self.SetBackgroundColour(wx.Colour(232, 232, 232)) mainSizer = wx.BoxSizer(wx.VERTICAL) self.primaryImage = ScaledImage(self, style=wx.BORDER_SUNKEN, size=(imageWidth, imageHeight)) self.beforeImage = ScaledImage(self, style=wx.BORDER_SUNKEN, size=(imageWidth, imageHeight)) self.afterImage = ScaledImage(self, style=wx.BORDER_SUNKEN, size=(imageWidth, imageHeight)) self.beforeAfterImages = [self.beforeImage, self.afterImage] # ------------------------------------------------------------------------------------------------ headerSizer = wx.BoxSizer(wx.HORIZONTAL) self.logo = Utils.GetPngBitmap("CrossMgrHeader.png") headerSizer.Add(wx.StaticBitmap(self, bitmap=self.logo)) self.title = wx.StaticText( self, label="CrossMgr Camera\nVersion {}".format(AppVerName.split()[1]), style=wx.ALIGN_RIGHT ) self.title.SetFont(wx.FontFromPixelSize(wx.Size(0, 28), wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_NORMAL)) headerSizer.Add(self.title, flag=wx.ALL, border=10) # ------------------------------------------------------------------------------ self.cameraDeviceLabel = wx.StaticText(self, label="Camera Device:") self.cameraDevice = wx.StaticText(self) boldFont = self.cameraDevice.GetFont() boldFont.SetWeight(wx.BOLD) self.cameraDevice.SetFont(boldFont) self.reset = wx.Button(self, label="Reset Camera") self.reset.Bind(wx.EVT_BUTTON, self.resetCamera) cameraDeviceSizer = wx.BoxSizer(wx.HORIZONTAL) cameraDeviceSizer.Add(self.cameraDeviceLabel, flag=wx.ALIGN_CENTRE_VERTICAL | wx.ALIGN_RIGHT) cameraDeviceSizer.Add(self.cameraDevice, flag=wx.ALIGN_CENTRE_VERTICAL | wx.LEFT, border=8) cameraDeviceSizer.Add(self.reset, flag=wx.ALIGN_CENTRE_VERTICAL | wx.LEFT, border=8) # ------------------------------------------------------------------------------ self.targetProcessingTimeLabel = wx.StaticText(self, label="Target Frames:") self.targetProcessingTime = wx.StaticText(self, label=u"{:.3f}".format(self.fps)) self.targetProcessingTime.SetFont(boldFont) self.targetProcessingTimeUnit = wx.StaticText(self, label="per sec") self.framesPerSecondLabel = wx.StaticText(self, label="Actual Frames:") self.framesPerSecond = wx.StaticText(self, label="25.000") self.framesPerSecond.SetFont(boldFont) self.framesPerSecondUnit = wx.StaticText(self, label="per sec") self.availableMsPerFrameLabel = wx.StaticText(self, label="Available Time Per Frame:") self.availableMsPerFrame = wx.StaticText(self, label=u"{:.0f}".format(1000.0 * self.frameDelay)) self.availableMsPerFrame.SetFont(boldFont) self.availableMsPerFrameUnit = wx.StaticText(self, label="ms") self.frameProcessingTimeLabel = wx.StaticText(self, label="Actual Frame Processing:") self.frameProcessingTime = wx.StaticText(self, label="20") self.frameProcessingTime.SetFont(boldFont) self.frameProcessingTimeUnit = wx.StaticText(self, label="ms") pfgs = wx.FlexGridSizer(rows=0, cols=6, vgap=4, hgap=8) fRight = wx.ALIGN_CENTRE_VERTICAL | wx.ALIGN_RIGHT fLeft = wx.ALIGN_CENTRE_VERTICAL # ------------------- Row 1 ------------------------------ pfgs.Add(self.targetProcessingTimeLabel, flag=fRight) pfgs.Add(self.targetProcessingTime, flag=fRight) pfgs.Add(self.targetProcessingTimeUnit, flag=fLeft) pfgs.Add(self.availableMsPerFrameLabel, flag=fRight) pfgs.Add(self.availableMsPerFrame, flag=fRight) pfgs.Add(self.availableMsPerFrameUnit, flag=fLeft) # ------------------- Row 2 ------------------------------ pfgs.Add(self.framesPerSecondLabel, flag=fRight) pfgs.Add(self.framesPerSecond, flag=fRight) pfgs.Add(self.framesPerSecondUnit, flag=fLeft) pfgs.Add(self.frameProcessingTimeLabel, flag=fRight) pfgs.Add(self.frameProcessingTime, flag=fRight) pfgs.Add(self.frameProcessingTimeUnit, flag=fLeft) statsSizer = wx.BoxSizer(wx.VERTICAL) statsSizer.Add(cameraDeviceSizer) statsSizer.Add(pfgs, flag=wx.TOP, border=8) headerSizer.Add(statsSizer, flag=wx.ALL, border=4) mainSizer.Add(headerSizer) self.messagesText = wx.TextCtrl( self, style=wx.TE_READONLY | wx.TE_MULTILINE | wx.HSCROLL, size=(350, imageHeight) ) self.messageManager = MessageManager(self.messagesText) border = 2 row1Sizer = wx.BoxSizer(wx.HORIZONTAL) row1Sizer.Add(self.primaryImage, flag=wx.ALL, border=border) row1Sizer.Add(self.messagesText, 1, flag=wx.TOP | wx.BOTTOM | wx.RIGHT | wx.EXPAND, border=border) mainSizer.Add(row1Sizer, 1, flag=wx.EXPAND) row2Sizer = wx.BoxSizer(wx.HORIZONTAL) row2Sizer.Add(self.beforeImage, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM, border=border) row2Sizer.Add(self.afterImage, flag=wx.RIGHT | wx.BOTTOM, border=border) mainSizer.Add(row2Sizer) # ------------------------------------------------------------------------------------------------ # Create a timer to update the frame loop. # self.timer = wx.Timer() self.timer.Bind(wx.EVT_TIMER, self.frameLoop) self.Bind(wx.EVT_CLOSE, self.onCloseWindow) self.readOptions() self.SetSizerAndFit(mainSizer) # Start the message reporting thread so we can see what is going on. self.messageThread = threading.Thread(target=self.showMessages) self.messageThread.daemon = True self.messageThread.start() self.grabFrameOK = False for i in [self.primaryImage, self.beforeImage, self.afterImage]: i.SetTestImage() # Start the frame loop. delayAdjustment = 0.80 if "win" in sys.platform else 0.98 ms = int(1000 * self.frameDelay * delayAdjustment) self.timer.Start(ms, False) def Start(self): self.messageQ.put(("", "************************************************")) self.messageQ.put(("started", now().strftime("%Y/%m/%d %H:%M:%S"))) self.startThreads() self.startCamera() def showMessages(self): while 1: message = self.messageQ.get() assert len(message) == 2, "Incorrect message length" cmd, info = message wx.CallAfter(self.messageManager.write, "{}: {}".format(cmd, info) if cmd else info) def startCamera(self): self.camera = None try: self.camera = Device(max(self.getCameraDeviceNum(), 0)) except Exception as e: self.messageQ.put(("camera", "Error: {}".format(e))) return False # self.camera.set_resolution( 640, 480 ) self.messageQ.put(("camera", "Successfully Connected: Device: {}".format(self.getCameraDeviceNum()))) for i in self.beforeAfterImages: i.SetTestImage() return True def startThreads(self): self.grabFrameOK = False self.listenerThread = SocketListener(self.requestQ, self.messageQ) error = self.listenerThread.test() if error: wx.MessageBox( "Socket Error:\n\n{}\n\nIs another CrossMgrVideo or CrossMgrCamera running on this computer?".format( error ), "Socket Error", wx.OK | wx.ICON_ERROR, ) wx.Exit() self.writerThread = threading.Thread(target=PhotoWriter, args=(self.writerQ, self.messageQ, self.ftpQ)) self.writerThread.daemon = True self.renamerThread = threading.Thread(target=PhotoRenamer, args=(self.renamerQ, self.writerQ, self.messageQ)) self.renamerThread.daemon = True self.ftpThread = threading.Thread(target=FTPWriter, args=(self.ftpQ, self.messageQ)) self.ftpThread.daemon = True self.fcb = FrameCircBuf(int(self.bufferSecs * self.fps)) self.listenerThread.start() self.writerThread.start() self.renamerThread.start() self.ftpThread.start() self.grabFrameOK = True self.messageQ.put(("threads", "Successfully Launched")) return True def frameLoop(self, event=None): if not self.grabFrameOK: return self.frameCount += 1 tNow = now() # Compute frame rate statistics. if self.frameCount == self.frameCountUpdate: self.fpsActual = self.frameCount / (tNow - self.tFrameCount).total_seconds() self.frameCount = 0 self.tFrameCount = tNow self.framesPerSecond.SetLabel(u"{:.3f}".format(self.fpsActual)) self.availableMsPerFrame.SetLabel(u"{:.0f}".format(1000.0 / self.fpsActual)) self.frameProcessingTime.SetLabel(u"{:.0f}".format(1000.0 * self.fpt.total_seconds())) try: image = self.camera.getImage() except Exception as e: self.messageQ.put(("error", "Webcam Failure: {}".format(e))) self.grabFrameOK = False return image = AddPhotoHeader( PilImageToWxImage(image), time=tNow, raceSeconds=(tNow - self.tLaunch).total_seconds(), bib=999, firstNameTxt=u"Firstname", lastNameTxt=u"LASTNAME", teamTxt=u"Team", raceNameTxt="Racename", ) self.fcb.append(tNow, image) wx.CallAfter(self.primaryImage.SetImage, image) # Process any save messages while 1: try: message = self.requestQ.get(False) except Empty: break tSearch = message["time"] advanceSeconds = message.get("advanceSeconds", 0.0) if advanceSeconds: tSearch += timedelta(seconds=advanceSeconds) times, frames = self.fcb.findBeforeAfter(tSearch, 1, 1) if not frames: self.messageQ.put( ("error", "No photos for {} at {}".format(message.get("bib", None), message["time"].isoformat())) ) lastImage = None for i, f in enumerate(frames): fname = GetPhotoFName( message["dirName"], message.get("bib", None), message.get("raceSeconds", None), i, times[i] ) image = AddPhotoHeader( f, message.get("bib", None), message.get("time", None), message.get("raceSeconds", None), message.get("firstName", u""), message.get("lastName", u""), message.get("team", u""), message.get("raceName", u""), ) # if lastImage is not None and image.GetData() == lastImage.GetData(): # self.messageQ.put( ('duplicate', '"{}"'.format(os.path.basename(fname))) ) # continue wx.CallAfter(self.beforeAfterImages[i].SetImage, image) SaveImage(fname, image, message.get("ftpInfo", None), self.messageQ, self.writerQ) lastImage = image if (now() - tNow).total_seconds() > self.frameDelay / 2.0: break self.fpt = now() - tNow self.tLast = tNow def shutdown(self): # Ensure that all images in the queue are saved. self.grabFrameOK = False if hasattr(self, "writerThread"): self.writerQ.put(("terminate",)) self.writerThread.join() def resetCamera(self, event): self.writeOptions() dlg = ConfigDialog(self, self.getCameraDeviceNum()) ret = dlg.ShowModal() cameraDeviceNum = dlg.GetCameraDeviceNum() dlg.Destroy() if ret != wx.ID_OK: return self.setCameraDeviceNum(cameraDeviceNum) self.grabFrameOK = self.startCamera() def setCameraDeviceNum(self, num): self.cameraDevice.SetLabel(unicode(num)) def getCameraDeviceNum(self): return int(self.cameraDevice.GetLabel()) def onCloseWindow(self, event): self.shutdown() wx.Exit() def writeOptions(self): self.config.Write("CameraDevice", self.cameraDevice.GetLabel()) self.config.Flush() def readOptions(self): self.cameraDevice.SetLabel(self.config.Read("CameraDevice", u"0"))