def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) # Maps status light names to the light field/pane index. self._nameToField = {} # type: typing.Dict[str, int] self._defaultBackgroundColour = self.GetBackgroundColour() self._notificationColour = wx.YELLOW listener = cockpit.gui.EvtEmitter(self, events.UPDATE_STATUS_LIGHT) listener.Bind(cockpit.gui.EVT_COCKPIT, self._OnNewStatus) # Some lights that we know we need. events.publish(events.UPDATE_STATUS_LIGHT, 'image count', '') events.publish(events.UPDATE_STATUS_LIGHT, 'device waiting', '')
def receiveData(self, action, *args): """This function is called when data is received from the hardware.""" # print 'receiveData received %s' % action if action == 'new image': (image, timestamp) = args transform = self.transform if transform.rot90: image = np.rot90(image, transform.rot90) if transform.flip_h: image = np.fliplr(image) if transform.flip_v: image = np.flipud(image) events.publish('new image %s' % self.name, image, timestamp)
def receiveData(self, *args): """This function is called when data is received from the hardware.""" (image, timestamp) = args if not isinstance(image, Exception): events.publish(events.NEW_IMAGE % self.name, image, timestamp) else: # Handle the dropped frame by publishing an empty image of the correct # size. Use the handler to fetch the size, as this will use a cached value, # if available. events.publish(events.NEW_IMAGE % self.name, np.zeros(self.handlers[0].getImageSize(), dtype=np.int16), timestamp) raise image
def updateMouseInfo(self, x, y): # Test that all required values have been populated. Use any(...), # because ```if None in [...]:``` will throw an exception when an # element in the list is an array with more than one element. if any(req is None for req in [self.imageData, self.imageShape, self.w, self.h]): return # First we have to convert from screen- to data-coordinates. coords = numpy.array(self.canvasToIndices(x, y), dtype=np.uint) shape = numpy.array(self.imageShape, dtype=np.uint) if (coords < shape).all() and (coords >= 0).all(): value = self.imageData[coords[0], coords[1]] events.publish("image pixel info", coords[::-1], value)
def setEnabled(self, shouldEnable=True): try: self.isEnabled = self.callbacks['setEnabled'](self.name, shouldEnable) except: self.isEnabled = False raise if self.isEnabled != shouldEnable: raise Exception("Problem enabling device with handler %s" % self) # Subscribe / unsubscribe to the prepare-for-experiment event. func = [events.unsubscribe, events.subscribe][shouldEnable] func('prepare for experiment', self.prepareForExperiment) events.publish(events.CAMERA_ENABLE, self, self.isEnabled)
def sendXYPositionUpdates(self): while True: prevX, prevY = self.positionCache[:2] x, y = self.getPosition(shouldUseCache=False)[:2] delta = abs(x - prevX) + abs(y - prevY) if delta < 2: # No movement since last time; done moving. for axis in [0, 1]: events.publish(events.STAGE_STOPPED, '%d nanomover' % axis) return for axis in [0, 1]: events.publish(events.STAGE_MOVER, axis) time.sleep(.1)
def sendXYPositionUpdates(self): while True: prevX, prevY = self.positionCache[:2] x, y, z = self.getPosition(shouldUseCache=False) delta = abs(x - prevX) + abs(y - prevY) if delta < 2: # No movement since last time; done moving. for axis in [0, 1]: events.publish('stage stopped', '%d nanomover' % axis) return for axis, val in enumerate([x, y]): events.publish('stage mover', '%d nanomover' % axis, axis, self.axisSignMapper[axis] * val) time.sleep(.1)
def publishPosition(self): for i in range(3): events.publish('stage mover', '%d nanomover' % i, i, (self.curPosition[i])) label = 'Stage up' color = (170, 170, 170) if 10000 < self.curPosition[2] < 16000: label = 'Stage middle' color = (255, 255, 0) elif self.curPosition[2] < 10000: label = 'Stage DOWN' color = (255, 0, 0) events.publish('update status light', 'stage vertical position', label, color)
def sendXYPositionUpdates(self): prevX, prevY, prevZ = self.xyPositionCache while True: x, y, z = self.getXYPosition(shouldUseCache=False) delta = abs(x - prevX) + abs(y - prevY) + abs(z - prevZ) if delta < .3: # No movement since last time; done moving. for axis in [0, 1, 2]: events.publish(events.STAGE_STOPPED, '%d PI mover' % axis) return for axis in [0, 1, 2]: events.publish(events.STAGE_MOVER, axis) (prevX, prevY, prevZ) = (x, y, z) time.sleep(0.1)
def sendXYPositionUpdates(self): prevX, prevY, prevZ = self.xyPositionCache while True: x, y, z = self.getXYPosition(shouldUseCache=False) delta = abs(x - prevX) + abs(y - prevY) + abs(z - prevZ) if delta < .3: # No movement since last time; done moving. for axis in [0, 1, 2]: events.publish('stage stopped', '%d PI mover' % axis) return for axis, val in enumerate([x, y, z]): events.publish('stage mover', '%d PI mover' % axis, axis, val) (prevX, prevY, prevZ) = (x, y, z) time.sleep(0.1)
def ChangeStepSize(self, direction: int) -> None: if direction == +1: guess_new = SensibleNextStepSize elif direction == -1: guess_new = SensiblePreviousStepSize else: raise ValueError( 'direction must be -1 (decrease) or +1 (increase)') old_step_sizes = self.GetStepSizes() new_step_sizes = tuple([guess_new(x) for x in old_step_sizes]) self._step_sizes[self.curHandlerIndex] = new_step_sizes for axis, step_size in enumerate(self.GetStepSizes()): events.publish('stage step size', axis, step_size)
def setEnabled(self, setState): if self.state == deviceHandler.STATES.constant != setState: if 'setExposing' in self.callbacks: self.callbacks['setExposing'](self.name, False) if setState == deviceHandler.STATES.constant: if self.state == setState: # Turn off the light self.callbacks['setEnabled'](self.name, False) # Update setState since used to set self.state later setState = deviceHandler.STATES.disabled events.publish(events.LIGHT_SOURCE_ENABLE, self, False) else: # Turn on the light continuously. self.callbacks['setEnabled'](self.name, True) if 'setExposing' in self.callbacks: self.callbacks['setExposing'](self.name, True) # We indicate that the light source is disabled to prevent # it being switched off by an exposure, but this event is # used to update controls, so we need to chain it with a # manual update. events.oneShotSubscribe( events.LIGHT_SOURCE_ENABLE, lambda *args: self.notifyListeners(self, setState)) events.publish(events.LIGHT_SOURCE_ENABLE, self, False) elif setState == deviceHandler.STATES.enabled: self.callbacks['setEnabled'](self.name, True) events.publish(events.LIGHT_SOURCE_ENABLE, self, True) else: self.callbacks['setEnabled'](self.name, False) events.publish(events.LIGHT_SOURCE_ENABLE, self, False) self.state = setState
def sendXYPositionUpdates(self): while True: prevX, prevY = self.xyPositionCache x, y = self.getXYPosition(shouldUseCache = False) delta = abs(x - prevX) + abs(y - prevY) if delta < 5.: # No movement since last time; done moving. for axis in [0, 1]: events.publish(events.STAGE_STOPPED, '%d PI mover' % axis) with self.xyLock: self.xyMotionTargets = [None, None] return for axis in [0, 1]: events.publish(events.STAGE_MOVER, axis) time.sleep(.01)
def wrapper(*args, **kwargs): wasInVideoMode = imager.amInVideoMode if wasInVideoMode: imager.shouldStopVideoMode = True tstart = time.time() while imager.amInVideoMode: time.sleep(0.05) if time.time() > tstart + 1.: print("Timeout pausing video mode - abort and restart.") events.publish(events.USER_ABORT) break result = func(*args, **kwargs) if wasInVideoMode: imager.videoMode() return result
def run(self): prevCounts = list(self.imagesReceived) self.updateText() while not self.shouldStop: if prevCounts != self.imagesReceived: # Have received new images since the last update; # update the display. with self.imageCountLock: self.updateText() prevCounts = list(self.imagesReceived) else: # No images; wait a bit. time.sleep(.1) # Clear the status light. events.publish(events.UPDATE_STATUS_LIGHT, 'image count', '')
def onExit(self): self._SaveWindowPositions() try: events.publish("user abort") except Exception as e: cockpit.util.logger.log.error("Error during logout: %s" % e) cockpit.util.logger.log.error(traceback.format_exc()) import cockpit.gui.loggingWindow cockpit.gui.loggingWindow.window.WriteToLogger(cockpit.util.logger.log) # Manually clear out any parent-less windows that still exist. This # can catch some windows that are spawned by WX and then abandoned, # typically because of bugs in the program. If we don't do this, then # sometimes the program will continue running, invisibly, and must # be killed via Task Manager. for window in wx.GetTopLevelWindows(): cockpit.util.logger.log.error("Destroying %s" % window) window.Destroy() # Call any deviec onExit code to, for example, close shutters and # switch of lasers. for dev in cockpit.depot.getAllDevices(): try: dev.onExit() except: pass # The following cleanup code used to be in main(), after App.MainLoop(), # where it was never reached. # HACK: manually exit the program. If we don't do this, then there's a small # possibility that non-daemonic threads (i.e. ones that don't exit when the # main thread exits) will hang around uselessly, forcing the program to be # manually shut down via Task Manager or equivalent. Why do we have non-daemonic # threads? That's tricky to track down. Daemon status is inherited from the # parent thread, and must be manually set otherwise. Since it's easy to get # wrong, we'll just leave this here to catch any failures to set daemon # status. badThreads = [] for thread in threading.enumerate(): if not thread.daemon: badThreads.append(thread) if badThreads: cockpit.util.logger.log.error("Still have non-daemon threads %s" % map(str, badThreads)) for thread in badThreads: cockpit.util.logger.log.error(str(thread.__dict__)) os._exit(0)
def deleteTilesList(self, tilesToDelete): for tile in tilesToDelete: tile.wipe() del self.tiles[self.tiles.index(tile)] self.SetCurrent(self.context) # Rerender all megatiles that are now invalid. dirtied = [] for megaTile in self.megaTiles: for tile in tilesToDelete: if megaTile.intersectsBox(tile.box): dirtied.append(megaTile) break self.rerenderMegatiles(dirtied) self.Refresh() events.publish('mosaic update')
def sendXYPositionUpdates(self): while True: prevX, prevY = self.xyPositionCache x, y = self.getXYPosition(shouldUseCache = False) delta = abs(x - prevX) + abs(y - prevY) if delta < 5.: # No movement since last time; done moving. for axis in [0, 1]: events.publish('stage stopped', '%d PI mover' % axis) with self.xyLock: self.xyMotionTargets = [None, None] return for axis, val in enumerate([x, y]): events.publish('stage mover', '%d PI mover' % axis, axis, self.axisSignMapper[axis] * val) curPosition = (x, y) time.sleep(.01)
def onSaveExposureSettings(self, name, event=None): dialog = wx.FileDialog( self, style=wx.FD_SAVE, wildcard='*.txt', defaultFile=name + '.txt', message="Please select where to save the settings.", defaultDir=cockpit.util.files.getUserSaveDir()) if dialog.ShowModal() != wx.ID_OK: # User cancelled. self.pathButton.setOption(name) return settings = dict() events.publish('save exposure settings', settings) handle = open(dialog.GetPath(), 'w') handle.write(json.dumps(settings)) handle.close() self.pathButton.setOption(name)
def waitFor(self, seconds): if seconds <= 0: return False print("Waiting for %.2f seconds" % seconds) endTime = time.time() + seconds curTime = time.time() while curTime < endTime and not self.shouldAbort: if int(curTime + .25) != int(curTime): remaining = endTime - curTime # Advanced to a new second; update the status light. displayMinutes = remaining // 60 displaySeconds = (remaining - displayMinutes * 60) // 1 events.publish(events.UPDATE_STATUS_LIGHT, 'device waiting', ('Waiting for %02d:%02d' % (displayMinutes, displaySeconds))) time.sleep(.25) curTime = time.time() return True
def sendXYZPositionUpdates(self): print("foo") while True: for ch in range(2): if self.xyzMotionTargets[ch] is not None: if (ctl.GetProperty_i32(d_handle, ch, ctl.Property.CHANNEL_STATE) & ctl.ChannelState.ACTIVELY_MOVING == 0): self.xyzMotionTargets[ch] = None events.publish(events.STAGE_STOPPED, '%d SmaractMover' % ch) print('stopped %d\n' % ch) else: #print('xyzMT '+str(ch)+' '+str(self.xyzMotionTargets)) events.publish(events.STAGE_MOVER, ch) time.sleep(0.1) if self.xyzMotionTargets[0] is None: if self.xyzMotionTargets[1] is None: if self.xyzMotionTargets[2] is None: print("bar") return
def setExposureTime(self, value, outermost=True): ## Set the exposure time on self and update that on lights # that share the same shutter if this is the outermost call. # \param value: new exposure time # \param outermost: flag indicating that we should update others. self.callbacks['setExposureTime'](self.name, value) # Publish event to update control labels. events.publish('light exposure update', self) # Update exposure times for lights that share the same shutter. s = self.__class__.__lightToShutter.get(self, None) self.exposureTime = value if s and outermost: if hasattr(s, 'setExposureTime'): s.setExposureTime(value) for other in self.__class__.__shutterToLights[s].difference([self ]): other.setExposureTime(value, outermost=False) events.publish('light exposure update', other)
def setPath(self, name): #store current path to text file if name == 'Save...': self.onSaveExposureSettings(self.currentPath) #load stored path elif name == 'Load...': self.onLoadExposureSettings() #update settings for current path elif name == 'Update' and self.currentPath != None: events.publish('save exposure settings', self.paths[self.currentPath]) self.pathButton.setOption(self.currentPath) #create newe stored path with current settings. elif name == 'New...': self.createNewPath() else: events.publish('load exposure settings', self.paths[name]) self.currentPath = name self.pathButton.setOption(name)
def videoMode(self): if not self.activeCameras: # No cameras, no video mode. events.publish(cockpit.events.VIDEO_MODE_TOGGLE, False) return if self.amInVideoMode: # Just cancel the current video mode. events.publish(cockpit.events.VIDEO_MODE_TOGGLE, False) self.stopVideo() return events.publish(cockpit.events.VIDEO_MODE_TOGGLE, True) self.shouldStopVideoMode = False self.amInVideoMode = True while not self.shouldStopVideoMode: if not self.activeLights: break # HACK: only wait for one camera. camera = list(self.activeCameras)[0] # Some cameras drop frames, i.e., takeImage() returns but # an image is never received. If that happens, videoMode # waits forever since there's no NEW_IMAGE event hence the # timeout. On top of the time to actual acquire the # image, we add 5 seconds for any processing and transfer # which should be more than enough (see issue #584). timeout = 5.0 + ( (camera.getExposureTime() + camera.getTimeBetweenExposures()) / 1000) try: events.executeAndWaitForOrTimeout(events.NEW_IMAGE % (camera.name), self.takeImage, timeout, shouldBlock=True, shouldStopVideo=False) except Exception as e: print("Video mode failed:", e) events.publish(cockpit.events.VIDEO_MODE_TOGGLE, False) traceback.print_exc() break self.amInVideoMode = False events.publish(cockpit.events.VIDEO_MODE_TOGGLE, False)
def onIdle(self, event): if self.pendingImages.empty(): # or not self.IsShownOnScreen(): return # Draw as many images as possible in 50ms. t = time.time() newTiles = [] self.SetCurrent(self.context) while not self.pendingImages.empty() and (time.time() - t < 0.05): data, pos, size, scalings, layer = self.pendingImages.get() newTiles.append(Tile(data, pos, size, scalings, layer)) self.tiles.extend(newTiles) for megaTile in self.megaTiles: megaTile.prerenderTiles(newTiles, self) self.tilesToRefresh.update(newTiles) self.Refresh() events.publish('mosaic update') if not self.pendingImages.empty(): event.RequestMore()
def toggleState(self, *args, **kwargs): if self.state == STATES.enabling: # Already processing a previous toggle request. return getIsEnabled = getattr(self, 'getIsEnabled', None) or self.callbacks.get( 'getIsEnabled', None) setEnabled = getattr(self, 'setEnabled', None) or self.callbacks.get( 'setEnabled', None) if not all([getIsEnabled, setEnabled]): raise Exception( 'toggleState dependencies not implemented for %s.' % self.name) # Do nothing if lock locked as en/disable already in progress. if not self.enableLock.acquire(False): return events.publish(events.DEVICE_STATUS, self, STATES.enabling) try: setEnabled(not (getIsEnabled())) except Exception as e: events.publish(events.DEVICE_STATUS, self, STATES.error) raise Exception('Problem encountered en/disabling %s:\n%s' % (self.name, e)) finally: self.enableLock.release() events.publish(events.DEVICE_STATUS, self, getIsEnabled())
def onLoadExposureSettings(self, event=None): dialog = wx.FileDialog( self, style=wx.FD_OPEN, wildcard='*.txt', message="Please select the settings file to load.", defaultDir=cockpit.util.files.getUserSaveDir()) if dialog.ShowModal() != wx.ID_OK: # User cancelled. self.pathButton.setOption(self.currentPath) return handle = open(dialog.GetPath(), 'r') modeName = os.path.splitext(os.path.basename(handle.name))[0] #get name for new mode # abuse get value dialog which will also return a string. name = cockpit.gui.dialogs.getNumberDialog.getNumberFromUser( parent=self.topPanel, default=modeName, title='New Path Name', prompt='Name') if name not in self.paths: self.pathList.append(name) self.paths[name] = json.loads('\n'.join(handle.readlines())) handle.close() events.publish('load exposure settings', self.paths[name]) #update button list self.pathButton.setOptions( map(lambda name: (name, lambda n=name: self.setPath(n)), self.pathList)) #and set button value. self.pathButton.setOption(name) self.currentPath = name # If we're using the listbox approach to show/hide light controls, # then make sure all enabled lights are shown and vice versa. if self.lightList is not None: for i, name in enumerate(self.lightList.GetItems()): handler = depot.getHandlerWithName(name) self.lightList.SetStringSelection(name, handler.getIsEnabled()) self.onLightSelect()
def prepareHandlers(self): # Store the pre-experiment altitude. self.initialAltitude = cockpit.interfaces.stageMover.getPosition()[-1] # Ensure that we're the only ones moving things around. cockpit.interfaces.stageMover.waitForStop() # TODO: Handling multiple movers on an axis is broken. Do not proceed if # anything but the innermost Z mover is selected. Needs a proper fix. if (cockpit.interfaces.stageMover.mover.curHandlerIndex < len(depot.getSortedStageMovers()[2]) - 1): wx.MessageBox("Wrong axis mover selected.") raise Exception("Wrong axis mover selected.") # Prepare our position. cockpit.interfaces.stageMover.goToZ(self.altBottom, shouldBlock=True) self.zStart = cockpit.interfaces.stageMover.getAllPositions()[-1][-1] events.publish('prepare for experiment', self) # Prepare cameras. for camera in self.cameras: # We set the expsoure time here. This needs to be set before # the action table is generated, since the action table # uses camera.getExposureTime to figure out timings. exposureTime = float(self.getExposureTimeForCamera(camera)) camera.setExposureTime(exposureTime)
def OnAddChannel(self, event: wx.CommandEvent) -> None: """Add current channel configuration to list.""" name = wx.GetTextFromUser('Enter name for new channel:', caption='Add new channel', parent=self) if not name: return new_channel = {} events.publish('save exposure settings', new_channel) if name not in self._channels: menu = event.GetEventObject() self.Bind(wx.EVT_MENU, self.OnApplyChannel, menu.Append(wx.ID_ANY, item=name)) else: answer = wx.MessageBox('There is already a channel named "%s".' ' Replace it?' % name, caption='Channel already exists', parent=self, style=wx.YES_NO) if answer != wx.YES: return self._channels[name] = new_channel
def imageSite(self, siteId, cycleNum, experimentStart): events.publish(events.UPDATE_STATUS_LIGHT, 'device waiting', 'Waiting for stage motion') cockpit.interfaces.stageMover.waitForStop() cockpit.interfaces.stageMover.goToSite(siteId, shouldBlock=True) self.waitFor(float(self.delayBeforeImaging.GetValue())) if self.shouldAbort: return # Try casting the site ID to an int, which it probably is, so that # we can use %03d (fills with zeros) instead of %s (variable width, # so screws with sorting). try: siteId = '%03d' % int(siteId) except ValueError: # Not actually an int. pass filename = "%s_t%03d_p%s_%s" % (time.strftime( '%Y%m%d-%H%M', experimentStart), cycleNum, siteId, self.fileBase.GetValue()) self.experimentPanel.setFilename(filename) start = time.time() events.executeAndWaitFor(events.EXPERIMENT_COMPLETE, self.experimentPanel.runExperiment) print("Imaging took %.2fs" % (time.time() - start))