def _acq_wait_data(self, timeout=0): """ Block until a data is received, or a stop message. Note: it expects that the acquisition is running. timeout (0<=float): how long to wait to check (use 0 to not wait) return (bool): True if needs to stop, False if data is ready raise TerminationRequested: if a terminate message was received """ tend = time.time() + timeout while True: left = max(0, tend - time.time()) try: msg = self._get_acq_msg(timeout=left) except queue.Empty: raise TimeoutError("No data message received within %s s" % (timeout,)) if msg == GEN_DATA: return False elif msg == GEN_TERM: raise TerminationRequested() elif msg == GEN_STOP: return True elif isinstance(msg, float): # trigger # received trigger too early => store it for later self._old_triggers.insert(0, msg) else: # Anything else shouldn't really happen logging.warning("Skipped message %s as acquisition is waiting for trigger", msg)
def _waitTemperatureReached(self, comp, timeout=None): """ Wait until the current temperature of the component has reached the target temperature (within some margin). comp (Component) timeout (0<float or None): maximum time to wait (in s) raises: TimeoutError: if time-out reached """ tstart = time.time() while timeout is None or time.time() < tstart + timeout: # TODO: adjust the timeout depending on whether the temperature # gets closer to the target over time or not. ttemp = comp.targetTemperature.value atemp = comp.temperature.value if atemp < ttemp + TEMP_EPSILON: return else: logging.debug( u"Waiting for temperature to reach %g °C (currently at %g °C)", ttemp, atemp) time.sleep(1) raise TimeoutError("Target temperature (%g C) not reached after %g s" % (comp.targetTemperature.value, timeout))
def _doWholeAcquisition(self, electron_coordinates, scale): """ Perform acquisition with one optical image for all the spots. It's faster, but it's harder to separate the spots. """ escan = self.escan ccd = self.ccd detector = self.detector dwell_time = self.dwell_time # order matters escan.scale.value = scale escan.resolution.value = self.repetitions escan.translation.value = (0, 0) # Scan at least 10 times, to avoids CCD/SEM synchronization problems sem_dt = escan.dwellTime.clip(dwell_time / 10) escan.dwellTime.value = sem_dt # For safety, ensure the exposure time is at least twice the time for a whole scan if dwell_time < 2 * sem_dt: dwell_time = 2 * sem_dt logging.info( "Increasing dwell time to %g s to avoid synchronization problems", dwell_time) # CCD setup ccd.binning.value = (1, 1) ccd.resolution.value = ccd.shape[0:2] et = numpy.prod(self.repetitions) * dwell_time ccd.exposureTime.value = et # s readout = numpy.prod(ccd.resolution.value) / ccd.readoutRate.value tot_time = et + readout + 0.05 try: if self._acq_state == CANCELLED: raise CancelledError() if self.bgsub: self.bg_image = ccd.data.get(asap=False) detector.data.subscribe(self._discard_data) self._min_acq_time = time.time() ccd.data.subscribe(self._onCCDImage) logging.debug("Scanning spot grid...") # Wait for CCD to capture the image if not self._ccd_done.wait(2 * tot_time + 4): raise TimeoutError("Acquisition of CCD timed out") with self._acq_lock: if self._acq_state == CANCELLED: raise CancelledError() logging.debug("Scan done.") self._acq_state = FINISHED finally: detector.data.unsubscribe(self._discard_data) ccd.data.unsubscribe(self._onCCDImage) return self._optical_image, electron_coordinates, scale
def _DoAcquisition(future, escan, ccd, detector, light): _sem_done.clear() try: if future._acq_state == CANCELLED: raise CancelledError() logging.debug("Acquiring CCD images...") # Turn on light for CCD acquisition intensities = [1, 0, 0, 0, 0, 0, 0] light.power.value = [ ints * pw for ints, pw in zip(intensities, light.power.range[1]) ] optical_image_1 = ccd.data.get() intensities = [0, 1, 0, 0, 0, 0, 0] light.power.value = [ ints * pw for ints, pw in zip(intensities, light.power.range[1]) ] optical_image_2 = ccd.data.get() intensities = [0, 0, 1, 0, 0, 0, 0] light.power.value = [ ints * pw for ints, pw in zip(intensities, light.power.range[1]) ] optical_image_3 = ccd.data.get() with _acq_lock: if future._acq_state == CANCELLED: raise CancelledError() logging.debug("Acquisition done.") future._acq_state = FINISHED # Turn off light for CCD acquisition light.power.value = light.power.range[0] logging.debug("Acquiring SEM image...") detector.data.subscribe(_ssOnSEMImage) # Wait for SEM to capture the image if not _sem_done.wait(2 * numpy.prod(escan.resolution.value) * escan.dwellTime.value + 4): raise TimeoutError("Acquisition of SEM timed out") detector.data.unsubscribe(_ssOnSEMImage) finally: detector.data.unsubscribe(_ssOnSEMImage) return optical_image_1, optical_image_2, optical_image_3, detector.data._electron_image
def acquire_roa(self, dataflow): """ Acquire the single field images that resemble the region of acquisition (ROA, megafield image). :param dataflow: (model.DataFlow) The dataflow on the detector. """ total_field_time = self._detector.frameDuration.value + 1.5 # there is about 1.5 seconds overhead per field # The first field is acquired twice, so the timeout must be at least twice the total field time. # Use 5 times the total field time to have a wide margin. timeout = 5 * total_field_time + 2 # Acquire all single field images, which are automatically offloaded to the external storage. for field_idx in self._roa.field_indices: # Reset the event that waits for the image being received (puts flag to false). self._data_received.clear() self.field_idx = field_idx logging.debug("Acquiring field with index: %s", field_idx) self.move_stage_to_next_tile( ) # move stage to next field image position self.correct_beam_shift( ) # correct the shift of the beams caused by the parasitic magnetic field. dataflow.next(field_idx) # acquire the next field image. # Wait until single field image data has been received (image_received sets flag to True). if not self._data_received.wait(timeout): # TODO here we often timeout when actually just the offload queue is full # need to handle offload queue error differently to just wait a bit instead of timing out # -> check if finish megafield is called in finally when hitting here raise TimeoutError("Timeout while waiting for field image.") self._fields_remaining.discard(field_idx) # In case the acquisition was cancelled by a client, before the future returned, raise cancellation error. # Note: The acquisition of the current single field image (tile) is still finished though. if self._cancelled: raise CancelledError() # Update the time left for the acquisition. expected_time = len(self._fields_remaining) * total_field_time self._future.set_progress(start=time.time(), end=time.time() + expected_time) logging.debug("Successfully acquired all fields of ROA.")
def acquire_roa(self, dataflow): """ Acquire the single field images that resemble the region of acquisition (ROA, megafield image). :param dataflow: (model.DataFlow) The dataflow on the detector. :return: (list of DataArrays): A list of the raw image data. Each data array (entire field, thumbnail, or zero array) represents one single field image within the ROA (megafield). """ total_field_time = self._detector.frameDuration.value timeout = total_field_time + 5 # TODO what margin should be used? # Acquire all single field images, which are automatically offloaded to the external storage. for field_idx in self._roa.field_indices: # Reset the event that waits for the image being received (puts flag to false). self._data_received.clear() self.field_idx = field_idx logging.debug("Acquiring field with index: %s", field_idx) self.move_stage_to_next_tile( ) # move stage to next field image position if field_idx != (0, 0): self.correct_beam_shift( ) # correct the shift of the beams caused by the parasitic magnetic field. dataflow.next(field_idx) # acquire the next field image. # Wait until single field image data has been received (image_received sets flag to True). if not self._data_received.wait(timeout): # TODO here we often timeout when actually just the offload queue is full # need to handle offload queue error differently to just wait a bit instead of timing out # -> check if finish megafield is called in finally when hitting here raise TimeoutError("Timeout while waiting for field image.") self._fields_remaining.discard(field_idx) # In case the acquisition was cancelled by a client, before the future returned, raise cancellation error. # Note: The acquisition of the current single field image (tile) is still finished though. if self._cancelled: raise CancelledError() # Update the time left for the acquisition. expected_time = len(self._fields_remaining) * total_field_time self._future.set_progress(end=time.time() + expected_time) return self.megafield
def move_stage_to_next_tile(self): """Move the stage to the next tile (field image) position.""" pos_hor, pos_vert = self.get_abs_stage_movement( ) # get the absolute position for the new tile f = self._stage.moveAbs({ 'x': pos_hor, 'y': pos_vert }) # move the stage timeout = 100 try: f.result(timeout=timeout) # don't wait forever logging.debug("Moved to stage position %s" % (self._stage.position.value, )) except TimeoutError: raise TimeoutError( "Stage movement to position (%s, %s) timed out after %s s." % (timeout, pos_hor, pos_vert))
def _changePressure(self, p): """ Synchronous change of the pressure p (float): target pressure """ if p["pressure"] == PRESSURE_VENTED: self.parent._device.VacVent() else: self.parent._device.VacPump() start = time.time() while not self.GetStatus() == 0: if (time.time() - start) >= VACUUM_TIMEOUT: raise TimeoutError("Vacuum action timed out") # Update chamber pressure until pumping/venting process is done self._updatePosition() self._position = p self._updatePosition()
def _acq_wait_data(self, exp_tend, timeout=0): """ Block until a data is received, or a stop message. Note: it expects that the acquisition is running. exp_tend (float): expected time the acquisition message is received timeout (0<=float): how long to wait to check (use 0 to not wait) return (bool): True if needs to stop, False if data is ready raise TerminationRequested: if a terminate message was received """ now = time.time() ttimeout = now + timeout while now <= ttimeout: twait = max(1e-3, (exp_tend - now) / 2) logging.debug("Waiting for %g s", twait) if self._acq_should_stop(twait): return True # Is the data ready? if self.CTCStatus(): logging.debug("Acq complete") return False now = time.time() raise TimeoutError("Acquisition timeout after %g s")
def _doSpotAcquisition(self, electron_coordinates, scale): """ Perform acquisition spot per spot. Slow, but works even if SEM FoV is small """ escan = self.escan ccd = self.ccd detector = self.detector dwell_time = self.dwell_time escan.scale.value = (1, 1) escan.resolution.value = (1, 1) # Set dt large enough so we unsubscribe before we even get an SEM # image (just to discard it) and start a second scan which would # cost in time. sem_dt = 2 * dwell_time escan.dwellTime.value = escan.dwellTime.clip(sem_dt) # CCD setup sem_shape = escan.shape[0:2] # sem ROI is ltrb sem_roi = (electron_coordinates[0][0] / sem_shape[0] + 0.5, electron_coordinates[0][1] / sem_shape[1] + 0.5, electron_coordinates[-1][0] / sem_shape[0] + 0.5, electron_coordinates[-1][1] / sem_shape[1] + 0.5) ccd_roi = self.sem_roi_to_ccd(sem_roi) self.configure_ccd(ccd_roi) if self.bgsub: _set_blanker(self.escan, True) self.bg_image = ccd.data.get(asap=False) _set_blanker(self.escan, False) et = dwell_time ccd.exposureTime.value = et # s readout = numpy.prod(ccd.resolution.value) / ccd.readoutRate.value tot_time = et + readout + 0.05 logging.debug("Scanning spot grid with image per spot procedure...") self._spot_images = [] for spot in electron_coordinates: self._ccd_done.clear() escan.translation.value = spot logging.debug("Scanning spot %s", escan.translation.value) try: if self._acq_state == CANCELLED: raise CancelledError() detector.data.subscribe(self._discard_data) ccd.data.subscribe(self._onSpotImage) # Wait for CCD to capture the image if not self._ccd_done.wait(2 * tot_time + 4): raise TimeoutError("Acquisition of CCD timed out") finally: detector.data.unsubscribe(self._discard_data) ccd.data.unsubscribe(self._onSpotImage) with self._acq_lock: if self._acq_state == CANCELLED: raise CancelledError() logging.debug("Scan done.") self._acq_state = FINISHED return self._spot_images, electron_coordinates, scale