def resetCams(self, curTime, cameras, table): resetEndTime = curTime for camera in cameras: exposureStart = max(curTime, self.getTimeWhenCameraCanExpose(table, camera)) # Cameras that have a pre-set exposure time can only use that # exposure time for clearing the sensor, hence why we take the # maximum of the min exposure time and the current exposure time. # \todo Is it possible for getExposureTime() to be less than # getMinExposureTime()? That would be a bug, right? minExposureTime = max(decimal.Decimal('.1'), camera.getMinExposureTime(isExact=True), camera.getExposureTime(isExact=True)) exposureMode = camera.getExposureMode() if exposureMode == cockpit.handlers.camera.TRIGGER_AFTER: table.addToggle(exposureStart + minExposureTime, camera) elif exposureMode == cockpit.handlers.camera.TRIGGER_DURATION: table.addAction(exposureStart, camera, True) table.addAction(exposureStart + minExposureTime, camera, False) else: # TRIGGER_BEFORE case table.addToggle(exposureStart, camera) resetEndTime = max(resetEndTime, exposureStart + minExposureTime) self.cameraToImageCount[camera] += 1 self.cameraToIgnoredImageIndices[camera].add( self.cameraToImageCount[camera]) self.cameraToIsReady[camera] = True return resetEndTime + decimal.Decimal('1e-6')
def testGetMinExposureTime(self): callback = unittest.mock.Mock(return_value=50) self.args['callbacks'] = {'getMinExposureTime' : callback} camera = cockpit.handlers.camera.CameraHandler(**self.args) self.assertEqual(camera.getMinExposureTime(), 50) callback.assert_called_with('mock')
def expose(self, curTime, cameras, lightTimePairs, table, pseudoGlobalExposure=False, previousMovementTime=0): # First, determine which cameras are not ready to be exposed, because # they may have seen light they weren't supposed to see (due to # bleedthrough from other cameras' exposures). These need # to be triggered (and we need to record that we want to throw away # those images) before we can proceed with the real exposure. camsToReset = set() for camera in cameras: if not self.cameraToIsReady[camera]: camsToReset.add(camera) if camsToReset: curTime = self.resetCams(curTime, camsToReset, table) # Figure out when we can start the exposure, based on the cameras # involved: their exposure modes, readout times, and last trigger # times determine how soon we can next trigger them (see # getTimeWhenCameraCanExpose() for more information). exposureStartTime = curTime # Adjust the exposure start based on when the cameras are ready. for camera in cameras: camExposureReadyTime = self.getTimeWhenCameraCanExpose( table, camera) # we add the readout time to get when the light should be trigger to # obtain pseudo global exposure camPseudoGlobalReadyTime = (camExposureReadyTime + self.cameraToReadoutTime[camera]) exposureStartTime = max(exposureStartTime, camExposureReadyTime) # Determine the maximum exposure time, which depends on our light # sources as well as how long we have to wait for the cameras to be # ready to be triggered. maxExposureTime = 0 if lightTimePairs: maxExposureTime = max(lightTimePairs, key=lambda a: a[1])[1] # Check cameras to see if they have minimum exposure times; take them # into account for when the exposure can end. Additionally, if they # are frame-transfer cameras, then we need to adjust maxExposureTime # to ensure that our triggering of the camera does not come too soon # (while it is still reading out the previous frame). for camera in cameras: maxExposureTime = max(maxExposureTime, camera.getMinExposureTime(isExact=True)) if camera.getExposureMode( ) == cockpit.handlers.camera.TRIGGER_AFTER: nextReadyTime = self.getTimeWhenCameraCanExpose(table, camera) # Ensure camera is exposing for long enough to finish reading # out the last frame. maxExposureTime = max(maxExposureTime, nextReadyTime - exposureStartTime) # Open the shutters for the specified exposure times, centered within # the max exposure time. # Note that a None value here means the user wanted to expose the # cameras without any special light. exposureEndTime = exposureStartTime + maxExposureTime for light, exposureTime, in lightTimePairs: if light is not None and light.name is not 'ambient': # i.e. not ambient light # Center the light exposure. timeSlop = maxExposureTime - exposureTime offset = timeSlop / 2 table.addAction(exposureEndTime - exposureTime - offset, light, True) table.addAction(exposureEndTime - offset, light, False) # Record this exposure time. if exposureTime not in self.lightToExposureTime[light]: self.lightToExposureTime[light].add(exposureTime) # Trigger the cameras. Keep track of which cameras we *aren't* using # here; if they are continuous-exposure cameras, then they may have # seen light that they shouldn't have, and need to be invalidated. usedCams = set() for camera in cameras: usedCams.add(camera) mode = camera.getExposureMode() if mode == cockpit.handlers.camera.TRIGGER_AFTER: table.addToggle(exposureEndTime, camera) elif mode == cockpit.handlers.camera.TRIGGER_DURATION: table.addAction(exposureStartTime, camera, True) table.addAction(exposureEndTime, camera, False) elif mode == cockpit.handlers.camera.TRIGGER_DURATION_PSEUDOGLOBAL: # We added some security time to the readout time that # we have to remove now cameraExposureStartTime = (exposureStartTime - self.cameraToReadoutTime[camera] - decimal.Decimal(0.005)) table.addAction(cameraExposureStartTime, camera, True) table.addAction(exposureEndTime, camera, False) elif mode == cockpit.handlers.camera.TRIGGER_BEFORE: table.addToggle(exposureStartTime, camera) elif mode == cockpit.handlers.camera.TRIGGER_SOFT: table.addAction(exposureStartTime, camera, True) else: raise Exception('%s has no trigger mode set.' % camera) self.cameraToImageCount[camera] += 1 for camera in self.cameras: if (camera not in usedCams and camera.getExposureMode() == cockpit.handlers.camera.TRIGGER_AFTER): # Camera is a continuous-exposure/frame-transfer camera # and therefore saw light it shouldn't have; invalidate it. self.cameraToIsReady[camera] = False return exposureEndTime