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()))) return True
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()) ) ) return True
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()))) return True
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 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
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 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.db = Database() self.fps = 25 self.frameDelay = 1.0 / self.fps self.bufferSecs = 10 self.xFinish = None self.tFrameCount = self.tLaunch = self.tLast = now() self.frameCount = 0 self.frameCountUpdate = int(self.fps * 2) self.fpsActual = 0.0 self.fpt = timedelta(seconds=0) self.iTriggerSelect = None self.triggerInfo = None self.captureTimer = wx.CallLater( 10, self.stopCapture ) self.fcb = FrameCircBuf( self.bufferSecs * self.fps ) self.config = wx.Config(appName="CrossMgrVideo", vendorName="SmartCyclingSolutions", style=wx.CONFIG_USE_LOCAL_FILE) self.requestQ = Queue() # Select photos from photobuf. self.dbWriterQ = Queue() # Photos waiting to be renamed and possibly ftp'd. self.dbReaderQ = Queue() # Photos read as requested from user. self.messageQ = Queue() # Collection point for all status/failure messages. self.SetBackgroundColour( wx.Colour(232,232,232) ) mainSizer = wx.BoxSizer( wx.VERTICAL ) #------------------------------------------------------------------------------------------------ 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 Video\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 ) self.manage = wx.Button( self, label="Manage Database" ) self.manage.Bind( wx.EVT_BUTTON, self.manageDatabase ) self.test = wx.Button( self, label="Test" ) self.test.Bind( wx.EVT_BUTTON, self.onTest ) 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 ) cameraDeviceSizer.Add( self.manage, flag=wx.ALIGN_CENTRE_VERTICAL|wx.LEFT, border=16 ) cameraDeviceSizer.Add( self.test, flag=wx.ALIGN_CENTRE_VERTICAL|wx.LEFT, border=16 ) #------------------------------------------------------------------------------ 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='/ 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='/ 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='Frame Processing Time:') 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.finishStrip = FinishStripPanel( self, size=(-1,wx.GetDisplaySize()[1]//2) ) self.finishStrip.finish.Bind( wx.EVT_RIGHT_DOWN, self.onRightClick ) self.primaryImage = ScaledImage( self, style=wx.BORDER_SUNKEN, size=(imageWidth, imageHeight) ) self.primaryImage.SetTestImage() hsDate = wx.BoxSizer( wx.HORIZONTAL ) hsDate.Add( wx.StaticText(self, label='Show Triggers for'), flag=wx.ALIGN_CENTER_VERTICAL ) tQuery = now() self.date = wx.DatePickerCtrl( self, dt=wx.DateTimeFromDMY( tQuery.day, tQuery.month-1, tQuery.year ), style=wx.DP_DROPDOWN|wx.DP_SHOWCENTURY ) self.date.Bind( wx.EVT_DATE_CHANGED, self.onQueryDateChanged ) hsDate.Add( self.date, flag=wx.LEFT, border=2 ) hsDate.Add( wx.StaticText(self, label='Filter by Bib'), flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=12 ) self.bib = wx.lib.intctrl.IntCtrl( self, style=wx.TE_PROCESS_ENTER, size=(64,-1), min=1, allow_none=True, value=None ) self.bib.Bind( wx.EVT_TEXT_ENTER, self.onQueryBibChanged ) hsDate.Add( self.bib, flag=wx.LEFT, border=2 ) self.tsQueryLower = datetime(tQuery.year, tQuery.month, tQuery.day) self.tsQueryUpper = self.tsQueryLower + timedelta(days=1) self.bibQuery = None self.triggerList = AutoWidthListCtrl( self, style=wx.LC_REPORT|wx.BORDER_SUNKEN|wx.LC_SORT_ASCENDING ) self.il = wx.ImageList(16, 16) self.sm_check = self.il.Add( Utils.GetPngBitmap('check_icon.png')) self.sm_close = self.il.Add( Utils.GetPngBitmap('flame_icon.png')) self.sm_up = self.il.Add( Utils.GetPngBitmap('SmallUpArrow.png')) self.sm_dn = self.il.Add( Utils.GetPngBitmap('SmallDownArrow.png')) self.triggerList.SetImageList(self.il, wx.IMAGE_LIST_SMALL) headers = ['Time', 'Bib', 'Name', 'Team', 'Wave'] for i, h in enumerate(headers): self.triggerList.InsertColumn(i, h, wx.LIST_FORMAT_RIGHT if h == 'Bib' else wx.LIST_FORMAT_LEFT) self.itemDataMap = {} self.triggerList.Bind( wx.EVT_LIST_ITEM_SELECTED, self.onTriggerSelected ) self.messagesText = wx.TextCtrl( self, style=wx.TE_READONLY|wx.TE_MULTILINE|wx.HSCROLL, size=(250,-1) ) self.messageManager = MessageManager( self.messagesText ) vsTriggers = wx.BoxSizer( wx.VERTICAL ) vsTriggers.Add( hsDate ) vsTriggers.Add( self.triggerList, 1, flag=wx.EXPAND|wx.TOP, border=2) #------------------------------------------------------------------------------------------------ mainSizer.Add( self.finishStrip, 1, flag=wx.EXPAND ) border = 2 row1Sizer = wx.BoxSizer( wx.HORIZONTAL ) row1Sizer.Add( self.primaryImage, flag=wx.ALL, border=border ) row1Sizer.Add( vsTriggers, 1, flag=wx.TOP|wx.BOTTOM|wx.RIGHT|wx.EXPAND, border=border ) row1Sizer.Add( self.messagesText, flag=wx.TOP|wx.BOTTOM|wx.RIGHT|wx.EXPAND, border=border ) mainSizer.Add( row1Sizer, flag=wx.EXPAND ) #------------------------------------------------------------------------------------------------ # 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 self.tsMax = None # 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 ) wx.CallLater( 300, self.refreshTriggers ) def onQueryDateChanged( self, event ): v = self.date.GetValue() self.tsQueryLower = datetime( v.GetYear(), v.GetMonth() + 1, v.GetDay() ) self.tsQueryUpper = self.tsQueryLower + timedelta( days=1 ) self.refreshTriggers( True ) def onQueryBibChanged( self, event ): self.bibQuery = self.bib.GetValue() self.refreshTriggers( True ) def GetListCtrl( self ): return self.triggerList def GetSortImages(self): return (self.sm_dn, self.sm_up) def getItemData( self, i ): data = self.triggerList.GetItemData( i ) return self.itemDataMap[data] def refreshTriggers( self, replace=False ): tNow = now() self.lastTriggerRefresh = tNow if replace: tsLower = self.tsQueryLower tsUpper = self.tsQueryUpper self.triggerList.DeleteAllItems() self.itemDataMap = {} self.tsMax = None self.iTriggerSelect = None self.triggerInfo = {} else: tsLower = (self.tsMax or datetime(tNow.year, tNow.month, tNow.day)) + timedelta(seconds=0.00001) tsUpper = tsLower + timedelta(days=1) triggers = self.db.getTriggers( tsLower, tsUpper, self.bibQuery ) if not triggers: return tsPrev = (self.tsMax or datetime(2000,1,1)) self.tsMax = triggers[-1][0] for i, (ts,bib,first_name,last_name,team,wave,race_name) in enumerate(triggers): closeFinish = ((ts-tsPrev).total_seconds() < 0.3) row = self.triggerList.InsertImageStringItem( sys.maxint, ts.strftime('%H:%M:%S.%f')[:-3], self.sm_close if closeFinish else self.sm_check ) if closeFinish and row > 0: self.triggerList.SetItemImage( row-1, self.sm_close ) self.triggerList.SetStringItem( row, 1, u'{:>6}'.format(bib) ) name = u', '.join( n for n in (last_name, first_name) if n ) self.triggerList.SetStringItem( row, 2, name ) self.triggerList.SetStringItem( row, 3, team ) self.triggerList.SetStringItem( row, 4, wave ) self.triggerList.SetItemData( row, row ) self.itemDataMap[row] = (ts,bib,name,team,wave,race_name,first_name,last_name) tsPrev = ts for i in xrange(5): self.triggerList.SetColumnWidth(i, wx.LIST_AUTOSIZE) self.triggerList.EnsureVisible( self.triggerList.GetItemCount() - 1 ) def Start( self ): self.messageQ.put( ('', '************************************************') ) self.messageQ.put( ('started', now().strftime('%Y/%m/%d %H:%M:%S')) ) self.startThreads() self.startCamera() def onTest( self, event ): self.testCount = getattr(self, 'testCount', 0) + 1 self.requestQ.put( { 'time':now(), 'bib':self.testCount, 'firstName':u'Test', 'lastName':u'Test', 'team':u'Test', 'wave':u'Test', 'raceName':u'Test', } ) wx.CallLater( 500, self.dbWriterQ.put, ('flush',) ) wx.CallLater( int(100+1000*int(tdCaptureBefore.total_seconds())), self.refreshTriggers ) def onRightClick( self, event ): if not self.triggerInfo: return self.xFinish = event.GetX() pd = PhotoDialog( self, self.finishStrip.finish.getJpg(self.xFinish), self.triggerInfo, self.finishStrip.GetTsJpgs(), self.fps ) pd.ShowModal() pd.Destroy() def setFinishStripJpgs( self, jpgs ): self.tsJpg = jpgs wx.CallAfter( self.finishStrip.SetTsJpgs, self.tsJpg, self.ts, self.triggerInfo ) def onTriggerSelected( self, event ): self.iTriggerSelect = event.m_itemIndex data = self.itemDataMap[self.triggerList.GetItemData(self.iTriggerSelect)] self.triggerInfo = { a:data[i] for i, a in enumerate(('ts','bib','name','team','wave','raceName','firstName','lastName')) } self.ts = data[0] self.dbReaderQ.put( ('getphotos', self.ts-tdCaptureBefore, self.ts+tdCaptureAfter) ) 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()) ) ) 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.dbWriterThread = threading.Thread( target=DBWriter, args=(self.dbWriterQ,) ) self.dbWriterThread.daemon = True self.dbReaderThread = threading.Thread( target=DBReader, args=(self.dbReaderQ, self.setFinishStripJpgs) ) self.dbReaderThread.daemon = True self.fcb = FrameCircBuf( int(self.bufferSecs * self.fps) ) self.listenerThread.start() self.dbWriterThread.start() self.dbReaderThread.start() self.grabFrameOK = True self.messageQ.put( ('threads', 'Successfully Launched') ) return True def stopCapture( self ): self.dbWriterQ.put( ('flush',) ) 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 = PilImageToWxImage( image ) # Add the image to the circular buffer. self.fcb.append( tNow, image ) # Update the monitor screen. if self.frameCount & 3 == 0: wx.CallAfter( self.primaryImage.SetImage, image ) # Record images if the timer is running. if self.captureTimer.IsRunning(): self.dbWriterQ.put( ('photo', tNow, image) ) # Periodically update events. if (tNow - self.lastTriggerRefresh).total_seconds() > 5.0: self.refreshTriggers() self.lastTriggerRefresh = tNow return # Process event messages while 1: try: message = self.requestQ.get(False) except Empty: break tSearch = message['time'] advanceSeconds = message.get('advanceSeconds', 0.0) tSearch += timedelta(seconds=advanceSeconds) # Record this trigger. self.dbWriterQ.put( ( 'trigger', tSearch - timedelta(seconds=advanceSeconds), message.get('bib', 99999), message.get('firstName',u''), message.get('lastName',u''), message.get('team',u''), message.get('wave',u''), message.get('raceName',u'') ) ) # If we are not currently capturing, make sure we record past frames. if not self.captureTimer.IsRunning(): times, frames = self.fcb.getBackFrames( tSearch - tdCaptureBefore ) for t, f in zip(times, frames): if f: self.dbWriterQ.put( ('photo', t, f) ) else: self.captureTimer.Stop() # Set a timer to stop recording after the capture window. millis = int((tSearch - now() + tdCaptureAfter).total_seconds() * 1000.0) if millis > 0: self.captureTimer.Start( millis ) 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, 'dbWriterThread'): self.dbWriterQ.put( ('terminate', ) ) self.dbWriterThread.join() self.dbReaderQ.put( ('terminate', ) ) self.dbReaderThread.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 manageDatabase( self, event ): dlg = ManageDatabase( self, self.db.getsize(), self.db.fname, title='Manage Database' ) if dlg.ShowModal() == wx.ID_OK: tsLower, tsUpper = dlg.GetDates() self.db.cleanBetween( tsLower, tsUpper ) wx.CallAfter( self.finishStrip.Clear ) wx.CallAfter( self.refreshTriggers, True ) dlg.Destroy() 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') )
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.db = Database() self.fps = 25 self.frameDelay = 1.0 / self.fps self.bufferSecs = 10 self.xFinish = None self.tFrameCount = self.tLaunch = self.tLast = now() self.frameCount = 0 self.frameCountUpdate = int(self.fps * 2) self.fpsActual = 0.0 self.fpt = timedelta(seconds=0) self.iTriggerSelect = None self.triggerInfo = None self.captureTimer = wx.CallLater(10, self.stopCapture) self.fcb = FrameCircBuf(self.bufferSecs * self.fps) self.config = wx.Config( appName="CrossMgrVideo", vendorName="SmartCyclingSolutions", #style=wx.CONFIG_USE_LOCAL_FILE ) self.requestQ = Queue() # Select photos from photobuf. self.dbWriterQ = Queue() # Photos waiting to be written self.dbReaderQ = Queue() # Photos read as requested from user. self.messageQ = Queue( ) # Collection point for all status/failure messages. self.SetBackgroundColour(wx.Colour(232, 232, 232)) mainSizer = wx.BoxSizer(wx.VERTICAL) #------------------------------------------------------------------------------------------------ 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 Video\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) self.reset = wx.Button(self, label="Reset Camera") self.reset.Bind(wx.EVT_BUTTON, self.resetCamera) self.manage = wx.Button(self, label="Manage Database") self.manage.Bind(wx.EVT_BUTTON, self.manageDatabase) self.test = wx.Button(self, label="Test") self.test.Bind(wx.EVT_BUTTON, self.onTest) self.focus = wx.Button(self, label="Focus...") self.focus.Bind(wx.EVT_BUTTON, self.onFocus) 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.reset, flag=wx.ALIGN_CENTRE_VERTICAL | wx.LEFT, border=32) cameraDeviceSizer.Add(self.manage, flag=wx.ALIGN_CENTRE_VERTICAL | wx.LEFT, border=16) cameraDeviceSizer.Add(self.test, flag=wx.ALIGN_CENTRE_VERTICAL | wx.LEFT, border=16) cameraDeviceSizer.Add(self.focus, flag=wx.ALIGN_CENTRE_VERTICAL | wx.LEFT, border=16) #------------------------------------------------------------------------------ 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='/ 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='/ 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='Frame Processing Time:') 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.finishStrip = FinishStripPanel(self, size=(-1, wx.GetDisplaySize()[1] // 2)) self.finishStrip.finish.Bind(wx.EVT_RIGHT_DOWN, self.onRightClick) self.primaryImage = ScaledImage(self, style=wx.BORDER_SUNKEN, size=(imageWidth, imageHeight)) self.primaryImage.SetTestImage() self.focusDialog = FocusDialog(self) hsDate = wx.BoxSizer(wx.HORIZONTAL) hsDate.Add(wx.StaticText(self, label='Show Triggers for'), flag=wx.ALIGN_CENTER_VERTICAL) tQuery = now() self.date = wx.adv.DatePickerCtrl( self, dt=wx.DateTime.FromDMY(tQuery.day, tQuery.month - 1, tQuery.year), style=wx.adv.DP_DROPDOWN | wx.adv.DP_SHOWCENTURY) self.date.Bind(wx.adv.EVT_DATE_CHANGED, self.onQueryDateChanged) hsDate.Add(self.date, flag=wx.LEFT, border=2) hsDate.Add(wx.StaticText(self, label='Filter by Bib'), flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=12) self.bib = wx.lib.intctrl.IntCtrl(self, style=wx.TE_PROCESS_ENTER, size=(64, -1), min=1, allow_none=True, value=None) self.bib.Bind(wx.EVT_TEXT_ENTER, self.onQueryBibChanged) hsDate.Add(self.bib, flag=wx.LEFT, border=2) self.tsQueryLower = datetime(tQuery.year, tQuery.month, tQuery.day) self.tsQueryUpper = self.tsQueryLower + timedelta(days=1) self.bibQuery = None self.triggerList = AutoWidthListCtrl( self, style=wx.LC_REPORT | wx.BORDER_SUNKEN | wx.LC_SORT_ASCENDING) self.il = wx.ImageList(16, 16) self.sm_close = [] for bm in getCloseFinishBitmaps(): self.sm_close.append(self.il.Add(bm)) self.sm_up = self.il.Add(Utils.GetPngBitmap('SmallUpArrow.png')) self.sm_up = self.il.Add(Utils.GetPngBitmap('SmallUpArrow.png')) self.sm_dn = self.il.Add(Utils.GetPngBitmap('SmallDownArrow.png')) self.triggerList.SetImageList(self.il, wx.IMAGE_LIST_SMALL) headers = ['Time', 'Bib', 'Name', 'Team', 'Wave', 'km/h', 'mph'] for i, h in enumerate(headers): self.triggerList.InsertColumn( i, h, wx.LIST_FORMAT_RIGHT if h in ('Bib', 'km/h', 'mph') else wx.LIST_FORMAT_LEFT) self.itemDataMap = {} self.triggerList.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onTriggerSelected) self.messagesText = wx.TextCtrl(self, style=wx.TE_READONLY | wx.TE_MULTILINE | wx.HSCROLL, size=(250, -1)) self.messageManager = MessageManager(self.messagesText) vsTriggers = wx.BoxSizer(wx.VERTICAL) vsTriggers.Add(hsDate) vsTriggers.Add(self.triggerList, 1, flag=wx.EXPAND | wx.TOP, border=2) #------------------------------------------------------------------------------------------------ mainSizer.Add(self.finishStrip, 1, flag=wx.EXPAND) border = 2 row1Sizer = wx.BoxSizer(wx.HORIZONTAL) row1Sizer.Add(self.primaryImage, flag=wx.ALL, border=border) row1Sizer.Add(vsTriggers, 1, flag=wx.TOP | wx.BOTTOM | wx.RIGHT | wx.EXPAND, border=border) row1Sizer.Add(self.messagesText, flag=wx.TOP | wx.BOTTOM | wx.RIGHT | wx.EXPAND, border=border) mainSizer.Add(row1Sizer, flag=wx.EXPAND) #------------------------------------------------------------------------------------------------ # 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 self.tsMax = None # 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) wx.CallLater(300, self.refreshTriggers) def onQueryDateChanged(self, event): v = self.date.GetValue() self.tsQueryLower = datetime(v.GetYear(), v.GetMonth() + 1, v.GetDay()) self.tsQueryUpper = self.tsQueryLower + timedelta(days=1) self.refreshTriggers(True) def onQueryBibChanged(self, event): self.bibQuery = self.bib.GetValue() self.refreshTriggers(True) def GetListCtrl(self): return self.triggerList def GetSortImages(self): return (self.sm_dn, self.sm_up) def getItemData(self, i): data = self.triggerList.GetItemData(i) return self.itemDataMap[data] def refreshTriggers(self, replace=False, iTriggerRow=None): tNow = now() self.lastTriggerRefresh = tNow if replace: tsLower = self.tsQueryLower tsUpper = self.tsQueryUpper self.triggerList.DeleteAllItems() self.itemDataMap = {} self.tsMax = None self.iTriggerSelect = None self.triggerInfo = {} else: tsLower = (self.tsMax or datetime( tNow.year, tNow.month, tNow.day)) + timedelta(seconds=0.00001) tsUpper = tsLower + timedelta(days=1) triggers = self.db.getTriggers(tsLower, tsUpper, self.bibQuery) if not triggers: return tsPrev = (self.tsMax or datetime(2000, 1, 1)) self.tsMax = triggers[-1][ 1] # id,ts,bib,first_name,last_name,team,wave,race_name,kmh for i, (id, ts, bib, first_name, last_name, team, wave, race_name, kmh) in enumerate(triggers): dtFinish = (ts - tsPrev).total_seconds() itemImage = self.sm_close[min( len(self.sm_close) - 1, int(len(self.sm_close) * dtFinish / closeFinishThreshold))] row = self.triggerList.InsertItem(sys.maxint, ts.strftime('%H:%M:%S.%f')[:-3], itemImage) self.triggerList.SetItem(row, 1, u'{:>6}'.format(bib)) name = u', '.join(n for n in (last_name, first_name) if n) self.triggerList.SetItem(row, 2, name) self.triggerList.SetItem(row, 3, team) self.triggerList.SetItem(row, 4, wave) if kmh: kmh_text, mph_text = u'{:.2f}'.format(kmh), u'{:.2f}'.format( kmh * 0.621371) else: kmh_text = mph_text = u'' self.triggerList.SetItem(row, 5, kmh_text) self.triggerList.SetItem(row, 6, mph_text) self.triggerList.SetItemData(row, row) self.itemDataMap[row] = (id, ts, bib, name, team, wave, race_name, first_name, last_name, kmh) tsPrev = ts for i in xrange(5): self.triggerList.SetColumnWidth(i, wx.LIST_AUTOSIZE) if iTriggerRow is not None and (0 <= iTriggerRow < self.triggerList.GetItemCount()): self.triggerList.EnsureVisible(iTriggerRow) self.triggerList.Select(iTriggerRow) else: self.triggerList.EnsureVisible(self.triggerList.GetItemCount() - 1) def Start(self): self.messageQ.put( ('', '************************************************')) self.messageQ.put(('started', now().strftime('%Y/%m/%d %H:%M:%S'))) self.startThreads() self.startCamera() def onTest(self, event): self.testCount = getattr(self, 'testCount', 0) + 1 self.requestQ.put({ 'time': now(), 'bib': self.testCount, 'firstName': u'Test', 'lastName': u'Test', 'team': u'Test', 'wave': u'Test', 'raceName': u'Test', }) wx.CallLater(500, self.dbWriterQ.put, ('flush', )) wx.CallLater(int(100 + 1000 * int(tdCaptureBefore.total_seconds())), self.refreshTriggers) def onFocus(self, event): self.focusDialog.Move((4, 4)) self.focusDialog.ShowModal() def onRightClick(self, event): if not self.triggerInfo: return self.xFinish = event.GetX() pd = PhotoDialog(self, self.finishStrip.finish.getJpg(self.xFinish), self.triggerInfo, self.finishStrip.GetTsJpgs(), self.fps) pd.ShowModal() if self.triggerInfo['kmh'] != (pd.kmh or 0.0): self.dbWriterQ.put(('kmh', self.triggerInfo['id'], pd.kmh or 0.0)) wx.CallLater(300, self.refreshTriggers, replace=True, iTriggerRow=self.iTriggerSelect) pd.Destroy() def setFinishStripJpgs(self, jpgs): self.tsJpg = jpgs wx.CallAfter(self.finishStrip.SetTsJpgs, self.tsJpg, self.ts, self.triggerInfo) def onTriggerSelected(self, event): self.iTriggerSelect = event.Index data = self.itemDataMap[self.triggerList.GetItemData( self.iTriggerSelect)] self.triggerInfo = { a: data[i] for i, a in enumerate(('id', 'ts', 'bib', 'name', 'team', 'wave', 'raceName', 'firstName', 'lastName', 'kmh')) } self.ts = self.triggerInfo['ts'] self.dbReaderQ.put( ('getphotos', self.ts - tdCaptureBefore, self.ts + tdCaptureAfter)) 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()))) 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.dbWriterThread = threading.Thread(target=DBWriter, args=(self.dbWriterQ, )) self.dbWriterThread.daemon = True self.dbReaderThread = threading.Thread(target=DBReader, args=(self.dbReaderQ, self.setFinishStripJpgs)) self.dbReaderThread.daemon = True self.fcb = FrameCircBuf(int(self.bufferSecs * self.fps)) self.listenerThread.start() self.dbWriterThread.start() self.dbReaderThread.start() self.grabFrameOK = True self.messageQ.put(('threads', 'Successfully Launched')) return True def stopCapture(self): self.dbWriterQ.put(('flush', )) 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 = PilImageToWxImage(image) # Add the image to the circular buffer. self.fcb.append(tNow, image) # Update the monitor screen. if self.frameCount & 3 == 0: wx.CallAfter(self.primaryImage.SetImage, image) if self.focusDialog.IsShown(): wx.CallAfter(self.focusDialog.SetImage, image) # Record images if the timer is running. if self.captureTimer.IsRunning(): self.dbWriterQ.put(('photo', tNow, image)) # Periodically update events. if (tNow - self.lastTriggerRefresh).total_seconds() > 5.0: self.refreshTriggers() self.lastTriggerRefresh = tNow return # Process event messages while 1: try: message = self.requestQ.get(False) except Empty: break tSearch = message['time'] advanceSeconds = message.get('advanceSeconds', 0.0) tSearch += timedelta(seconds=advanceSeconds) # Record this trigger. self.dbWriterQ.put( ('trigger', tSearch - timedelta(seconds=advanceSeconds), message.get('bib', 99999), message.get('firstName', u''), message.get('lastName', u''), message.get('team', u''), message.get('wave', u''), message.get('raceName', u''))) # If we are not currently capturing, make sure we record past frames. if not self.captureTimer.IsRunning(): times, frames = self.fcb.getBackFrames(tSearch - tdCaptureBefore) for t, f in zip(times, frames): if f: self.dbWriterQ.put(('photo', t, f)) else: self.captureTimer.Stop() # Set a timer to stop recording after the capture window. millis = int( (tSearch - now() + tdCaptureAfter).total_seconds() * 1000.0) if millis > 0: self.captureTimer.Start(millis) 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, 'dbWriterThread'): self.dbWriterQ.put(('terminate', )) self.dbWriterThread.join() self.dbReaderQ.put(('terminate', )) self.dbReaderThread.join() def resetCamera(self, event=None): 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 manageDatabase(self, event): dlg = ManageDatabase(self, self.db.getsize(), self.db.fname, title='Manage Database') if dlg.ShowModal() == wx.ID_OK: tsLower, tsUpper = dlg.GetDates() self.db.cleanBetween(tsLower, tsUpper) wx.CallAfter(self.finishStrip.Clear) wx.CallAfter(self.refreshTriggers, True) dlg.Destroy() 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, 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"))