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 self.experimentPanel.filepath_panel.UpdateFilename({ "date": time.strftime('%Y%m%d', experimentStart), "time": time.strftime('%H%M', experimentStart), "cycle": ("%03d" % cycleNum), "site": siteId, }) start = time.time() events.executeAndWaitFor(events.EXPERIMENT_COMPLETE, self.experimentPanel.runExperiment) print(f"Imaging took {(time.time() - start):.2f} seconds")
def executeTable(self, table, startIndex, stopIndex, numReps, repDuration): actions = actions_from_table(table, startIndex, stopIndex, repDuration) events.publish(events.UPDATE_STATUS_LIGHT, 'device waiting', 'Waiting for DSP to finish') self.connection.PrepareActions(actions, numReps) events.executeAndWaitFor(events.EXECUTOR_DONE % self.name, self.connection.RunActions) events.publish(events.EXPERIMENT_EXECUTION) return
def snapImage(self): #check that we have a camera and light source cams = 0 lights = 0 cams = len(depot.getActiveCameras()) for light in depot.getHandlersOfType(depot.LIGHT_TOGGLE): if light.getIsEnabled(): lights = lights + 1 if (cams is 0) or (lights is 0): print("Snap needs a light and a camera to opperate") return #take the image events.executeAndWaitFor( events.NEW_IMAGE % (list(cockpit.interfaces.imager.imager.activeCameras)[0].name), cockpit.interfaces.imager.imager.takeImage, shouldStopVideo=False) mosaic.transferCameraImage() self.Refresh()
def imageSite(self, siteId, cycleNum, experimentStart): events.publish('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('experiment complete', self.experimentPanel.runExperiment) print("Imaging took %.2fs" % (time.time() - start))
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 start = time.time() try: # HACK: only wait for one camera. events.executeAndWaitFor("new image %s" % (list(self.activeCameras)[0].name), self.takeImage, 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) # Our thread could be blocked waiting for an image. # Clear one shot new image subscribers to make sure it # is unblocked. events.clearOneShotSubscribers(pattern="new image")
def execute(self): cockpit.util.logger.log.info("Experiment.execute started.") # Iteratively find the ExperimentExecutor that can tackle the largest # portion of self.table, have them run it, and wait for them to finish. executors = depot.getHandlersOfType(depot.EXECUTOR) self.shouldAbort = False for rep in range(self.numReps): startTime = time.time() repDuration = None curIndex = 0 shouldStop = False # Need to track delay introduced by dropping back to software timing. delay = 0. while curIndex < len(self.table): if curIndex > 0: # Update the delay nextTime = delay + startTime + float( self.table[curIndex][0]) / 1000. delay += max(0, time.time() - nextTime) if self.shouldAbort: cockpit.util.logger.log.error( "Cancelling on rep %d after %d actions due to user abort" % (rep, curIndex)) break best = None bestLen = 0 for executor in executors: numLines = executor.getNumRunnableLines( self.table, curIndex) if best is None or numLines > bestLen: best = executor bestLen = numLines numReps = 1 if bestLen == len(self.table): # This executor can handle the entire experiment, so we # should tell them to handle the repeats as well. numReps = self.numReps shouldStop = True # Expand from seconds to milliseconds repDuration = self.repDuration * 1000 if bestLen == 0: # No executor can run this line. See if we can fall back to software. fn = None t, h, action = self.table[curIndex] if h.deviceType == depot.CAMERA and 'softTrigger' in h.callbacks: fn = lambda: h.callbacks['softTrigger']() elif h.deviceType == depot.STAGE_POSITIONER: fn = lambda: h.moveAbsolute(action) if fn is None: raise RuntimeError( "Found a line that no executor could handle: %s" % str(self.table.actions[curIndex])) # Wait until this action is due. if curIndex > 0: timeToNext = delay + startTime + float( self.table[curIndex][0]) / 1000. - time.time() time.sleep(max(0, timeToNext)) fn() curIndex += 1 else: # Don't resume execution too early. # TODO: would be better to pass a 'do not start before' argument # to the handler, so any work it has to do does not add further # delays. if curIndex > 0: timeToNext = delay + startTime + float( self.table[curIndex][0]) / 1000. - time.time() time.sleep(max(0, timeToNext)) events.executeAndWaitFor('experiment execution', best.executeTable, self.table, curIndex, curIndex + bestLen, numReps, repDuration) curIndex += bestLen if shouldStop: # All reps handled by an executor. cockpit.util.logger.log.debug("Stopping now at %.2f" % time.time()) break # Wait for the end of the rep. if rep != self.numReps - 1: waitTime = self.repDuration - (time.time() - startTime) time.sleep(max(0, waitTime)) ## TODO: figure out how long we should wait for the last captures to complete. # For now, wait 1s. time.sleep(1.) cockpit.util.logger.log.info("Experiment.execute completed.") return True
def executeTable(self, table, startIndex, stopIndex, numReps, repDuration): # Take time and arguments (i.e. omit handler) from table to generate actions. # For the UCSF m6x DSP device, we also need to: # - make the analogue values offsets from the current position; # - convert float in ms to integer clock ticks and ensure digital # lines are not changed twice on the same tick; # - separate analogue and digital events into different lists; # - generate a structure that describes the profile. actions = actions_from_table(table, startIndex, stopIndex, repDuration) # Profiles analogs = [ [], [], [], [] ] # A list of lists (one per channel) of tuples (ticks, (analog values)) digitals = [] # A list of tuples (ticks, digital state) # Need to track time of last analog events to workaround a # DSP bug later. Also used to detect when events exceed timing # resolution tLastA = None # The DSP executes an analogue movement profile, which is defined using # offsets relative to a baseline at the time the profile was initialized. # These offsets are encoded as unsigned integers, so at profile # intialization, each analogue channel must be at or below the lowest # value it needs to reach in the profile. lowestAnalogs = list(np.amin([x[1][1] for x in actions], axis=0)) for line, lowest in enumerate(lowestAnalogs): if lowest < self._lastAnalogs[line]: self._lastAnalogs[line] = lowest self.setAnalog(line, lowest) for (t, (darg, aargs)) in actions: # Convert t to ticks as int while rounding up. The rounding is # necessary, otherwise e.g. 10.1 and 10.1999999... both result in 101. ticks = int(float(t) * self.tickrate + 0.5) # Digital actions - one at every time point. if len(digitals) == 0: digitals.append((ticks, darg)) elif ticks == digitals[-1][0]: # Used to check for conflicts here, but that's not so trivial. # We need to allow several bits to change at the same time point, but # they may show up as multiple events in the actionTable. For now, just # take the most recent state. if darg != digitals[-1][1]: digitals[-1] = (ticks, darg) else: pass else: digitals.append((ticks, darg)) # Analogue actions - only enter into profile on change. # DSP uses offsets from value when the profile was loaded. offsets = map(lambda base, new: new - base, self._lastAnalogs, aargs) for offset, a in zip(offsets, analogs): if ((len(a) == 0) or (len(a) > 0 and offset != a[-1][1])): a.append((ticks, offset)) tLastA = t # Work around some DSP bugs: # * The action table needs at least two events to execute correctly. # * Last action must be digital --- if the last analog action is at the same # time or after the last digital action, it will not be performed. # Both can be avoided by adding a digital action that does nothing. if len(digitals) == 1 or tLastA >= digitals[-1][0]: # Just duplicate the last digital action, one tick later. digitals.append((digitals[-1][0] + 1, digitals[-1][1])) # Update records of last positions. self._lastDigital = digitals[-1][1] self._lastAnalogs = list( map(lambda x, y: x - (y[-1:][1:] or 0), self._lastAnalogs, analogs)) events.publish(events.UPDATE_STATUS_LIGHT, 'device waiting', 'Waiting for DSP to finish') # Convert digitals to array of uints. digitalsArr = np.array(digitals, dtype=np.uint32).reshape(-1, 2) # Convert analogs to array of uints. analogsArr = [ np.array(a, dtype=np.uint32).reshape(-1, 2) for a in analogs ] # Create a description dict. Will be byte-packed by server-side code. maxticks = max( chain([d[0] for d in digitals], [a[0] for a in chain.from_iterable(analogs)])) description = {} description['count'] = maxticks description['clock'] = 1000. / float(self.tickrate) description['InitDio'] = self._lastDigital description['nDigital'] = len(digitals) description['nAnalog'] = [len(a) for a in analogs] self._lastProfile = (description, digitalsArr, analogsArr) self.connection.profileSet(description, digitalsArr, *analogsArr) self.connection.DownloadProfile() self.connection.InitProfile(numReps) events.executeAndWaitFor(events.EXECUTOR_DONE % self.name, self.connection.trigCollect) events.publish(events.EXPERIMENT_EXECUTION)
def executeRep(self, repNum): # Get all light sources that the microscope has. allLights = depot.getHandlersOfType(depot.LIGHT_TOGGLE) # getHandlersOfType returns an unordered set datatype. If we want to # index into allLights, we need to convert it to a list first. allLights = list(allLights) # Print the names of all light sources. for light in allLights: print(light.name) # Get all power controls for light sources. allLightPowers = depot.getHandlersOfType(depot.LIGHT_POWER) # Get all camera handlers that the microscope has, and filter it # down to the ones that are currently active. allCameras = depot.getHandlersOfType(depot.CAMERA) # Create a new empty list. activeCams = [] for camera in allCameras: if camera.getIsEnabled(): # Camera is enabled. activeCams.append(camera) # Get a specific light. led650 = depot.getHandlerWithName("650 LED") # Get a specific light's power control (ditto). led650power = depot.getHandlerWithName("650 LED power") # Set the output power to use for this light source, when it is active. led650power.setPower(2.5) # Set this light source to be continually exposing. led650.setExposing(True) # Wait for some time (1.5 seconds in this case). time.sleep(1.5) # Set this light source to stop continually exposing. led650.setExposing(False) # Get another light source. laser488 = depot.getHandlerWithName("488 L") # Set this light source to be enabled when we take images. # Note: for lasers, an AOM in the laser box that acts as a light # shutter is automatically adjusted when you enable/disable lights. # I don't know how well enabling multiple lasers simultaneously works. # Note: lasers, the DIA light source, and the EPI light source, are # mutually exclusive as they use different shutters and only one # shutter can be active at a time for some unknown reason. laser488.setEnabled(True) # Take images, using all current active camera views and light # sources; wait for the image (and time of acquisition) from the named # camera to be available. # Note: The light sources selected automatically use the emission # filter you have set in the UI. If multiple lights use the same # emission filter, then they will expose simultaneously (if possible). # Note: that if you try to wait for an image # that will never arrive (e.g. for the wrong camera name) then your # script will get stuck at this point. # Note: you must have at least one light source enabled for any # image to be taken! eventName = 'new image %s' % activeCams[0].name image, timestamp = events.executeAndWaitFor( eventName, cockpit.interfaces.imager.takeImage, shouldBlock=True) # Get the min, max, median, and standard deviation of the image imageMin = image.min() imageMax = image.max() imageMedian = numpy.median(image) imageStd = numpy.std(image) print("Image stats:", imageMin, imageMax, imageMedian, imageStd) # Some miscellaneous functions below. # Get the current stage position; positions are in microns. curX, curY, curZ = cockpit.interfaces.stageMover.getPosition() # Move to a new Z position, and wait until we arrive. cockpit.interfaces.stageMover.goToZ(curZ + 5, shouldBlock=True) # Move to a new XY position. # Note: the goToXY function expects a "tuple" for the position, # hence the extra parentheses (i.e. "goToXY(x, y)" is invalid; # "goToXY((x, y))" is correct). cockpit.interfaces.stageMover.goToXY((curX + 50, curY - 50), shouldBlock=True) # Get the device responsible for the dichroics and light sources lightsDevice = depot.getDevice(cockpit.devices.lights) # Set a new filter/dichroic for the lower turret. lightsDevice.setFilter(isFirstFilter=True, label="2-488 L") # Set a new filter/dichroic for the upper turret. lightsDevice.setFilter(isFirstFilter=False, label="6-600bp")
def executeRep(self, repNum): # Get all light sources that the microscope has. allLights = depot.getHandlersOfType(depot.LIGHT_TOGGLE) # getHandlersOfType returns an unordered set datatype. If we want to # index into allLights, we need to convert it to a list first. allLights = list(allLights) # Print the names of all light sources. for light in allLights: print(light.name) # Get all power controls for light sources. allLightPowers = depot.getHandlersOfType(depot.LIGHT_POWER) # Get all light source filters. allLightFilters = depot.getHandlersOfType(depot.LIGHT_FILTER) # Get all camera handlers that the microscope has, and filter it # down to the ones that are currently active. allCameras = depot.getHandlersOfType(depot.CAMERA) # Create a new empty list. activeCams = [] for camera in allCameras: if camera.getIsEnabled(): # Camera is enabled. activeCams.append(camera) # Get a specific light. deepstar405 = depot.getHandlerWithName("488 Deepstar") deepstar405power = depot.getHandlerWithName("488 Deepstar power") # Set the output power to use for this light source, when it is active. deepstar405power.setPower(15) # Get another light source. The "\n" in the name is a newline, which # was inserted (when this light source handler was created) to make # the light control button look nice. laser488 = depot.getHandlerWithName("488\nlight") # Set this light source to be enabled when we take images. laser488.setEnabled(True) # Take images, using all current active camera views and light # sources; wait for the image (and time of acquisition) from the named # camera to be available. # Note: that if you try to wait for an image # that will never arrive (e.g. for the wrong camera name) then your # script will get stuck at this point. eventName = 'new image %s' % activeCams[0].name image, timestamp = events.executeAndWaitFor( eventName, cockpit.interfaces.imager.takeImage, shouldBlock=True) # Get the min, max, median, and standard deviation of the image imageMin = image.min() imageMax = image.max() imageMedian = numpy.median(image) imageStd = numpy.std(image) print("Image stats:", imageMin, imageMax, imageMedian, imageStd) # Some miscellaneous functions below. # Get the current stage position; positions are in microns. curX, curY, curZ = cockpit.interfaces.stageMover.getPosition() # Move to a new Z position, and wait until we arrive. cockpit.interfaces.stageMover.goToZ(curZ + 5, shouldBlock=True) # Move to a new XY position. # Note: the goToXY function expects a "tuple" for the position, # hence the extra parentheses (i.e. "goToXY(x, y)" is invalid; # "goToXY((x, y))" is correct). cockpit.interfaces.stageMover.goToXY((curX + 50, curY - 50), shouldBlock=True)