def test_acq_getScalingTable(self): """Get the scaling table (correction for mapping vertical px with timestamps) for the streak Time Range chosen for one sweep.""" # test a first value self.streakunit.timeRange.value = util.find_closest( 0.000000002, self.streakunit.timeRange.choices) # 2ns self.streakunit.streakMode.value = True # Note: RemoteEx automatically stops and restarts "Live" acq when changing settings img = self.readoutcam.data.get() self.assertIn(model.MD_TIME_LIST, img.metadata) self.assertIsNotNone(img.metadata[model.MD_TIME_LIST]) # check first value in table is the same order as the conversion factor # the quotient should be greater than zero firstCorrectedValue = img.metadata[model.MD_TIME_LIST][0] conversionFactor = self.streakunit.timeRangeFactor self.assertGreater(firstCorrectedValue / conversionFactor, 0) # test a second value # test a second value) self.streakunit.timeRange.value = util.find_closest( 0.001, self.streakunit.timeRange.choices) # 1ms img = self.readoutcam.data.get() self.assertIn(model.MD_TIME_LIST, img.metadata) self.assertIsNotNone(img.metadata[model.MD_TIME_LIST]) # check first value in table is the same order as the conversion factor # the quotient should be greater than zero firstCorrectedValue = img.metadata[model.MD_TIME_LIST][0] conversionFactor = self.streakunit.timeRangeFactor self.assertGreater(firstCorrectedValue / conversionFactor, 0) # check that scaling correction is not included when image is acquired in Focus mode # Note: In case we only acquire images in operate mode, we can skip that test. self.streakunit.streakMode.value = False img = self.readoutcam.data.get() self.assertNotIn(model.MD_TIME_LIST, img.metadata) self.assertFalse(img.metadata[model.MD_STREAK_MODE]) # change again to operate mode self.streakunit.streakMode.value = True img = self.readoutcam.data.get() self.assertIn(model.MD_TIME_LIST, img.metadata) self.assertIsNotNone(img.metadata[model.MD_TIME_LIST]) # check first value in table is the same order as the conversion factor # the quotient should be greater than zero firstCorrectedValue = img.metadata[model.MD_TIME_LIST][0] conversionFactor = self.streakunit.timeRangeFactor self.assertGreater(firstCorrectedValue / conversionFactor, 0)
def test_acq_getScalingTable(self): """Get the scaling table (correction for mapping vertical px with timestamps) for the streak Time Range chosen for one sweep.""" # test a first value self.streakunit.timeRange.value = util.find_closest(0.000000002, self.streakunit.timeRange.choices) # 2ns self.streakunit.streakMode.value = True # Note: RemoteEx automatically stops and restarts "Live" acq when changing settings img = self.readoutcam.data.get() self.assertIn(model.MD_TIME_LIST, img.metadata) self.assertIsNotNone(img.metadata[model.MD_TIME_LIST]) # check first value in table is the same order as the conversion factor # the quotient should be greater than zero firstCorrectedValue = img.metadata[model.MD_TIME_LIST][0] conversionFactor = self.streakunit.timeRangeFactor self.assertGreater(firstCorrectedValue/conversionFactor, 0) # test a second value # test a second value) self.streakunit.timeRange.value = util.find_closest(0.001, self.streakunit.timeRange.choices) # 1ms img = self.readoutcam.data.get() self.assertIn(model.MD_TIME_LIST, img.metadata) self.assertIsNotNone(img.metadata[model.MD_TIME_LIST]) # check first value in table is the same order as the conversion factor # the quotient should be greater than zero firstCorrectedValue = img.metadata[model.MD_TIME_LIST][0] conversionFactor = self.streakunit.timeRangeFactor self.assertGreater(firstCorrectedValue/conversionFactor, 0) # check that scaling correction is not included when image is acquired in Focus mode # Note: In case we only acquire images in operate mode, we can skip that test. self.streakunit.streakMode.value = False img = self.readoutcam.data.get() self.assertNotIn(model.MD_TIME_LIST, img.metadata) self.assertFalse(img.metadata[model.MD_STREAK_MODE]) # change again to operate mode self.streakunit.streakMode.value = True img = self.readoutcam.data.get() self.assertIn(model.MD_TIME_LIST, img.metadata) self.assertIsNotNone(img.metadata[model.MD_TIME_LIST]) # check first value in table is the same order as the conversion factor # the quotient should be greater than zero firstCorrectedValue = img.metadata[model.MD_TIME_LIST][0] conversionFactor = self.streakunit.timeRangeFactor self.assertGreater(firstCorrectedValue/conversionFactor, 0)
def test_acq_Live_RingBuffer_subscribe(self): """Acquire single image and receive it via the dataport.""" # Note: AcqStop can be called multiple times even if the acq is already stopped without causing an error # However, AcqStart can be only called once and raises an error if called while an acq is running. # AcqStop is not an asynchronous command. But it takes time until the status of the async command # "AcqStart" is properly finished. # Changing settings is not async. RemoteEx blocks as long as the settings are changed. # RemoteEx also stops the "Live" mode if a settings change is requested but does not restarts the "Live" mode. self.streakunit.streakMode.value = True self.streakunit.timeRange.value = util.find_closest(0.000000002, self.streakunit.timeRange.choices) self.streakunit.MCPGain.value = 2 self.readoutcam.exposureTime.value = 0.1 # 100ms time.sleep(1) # start Live mode # and subscribe to dataflow afterwards in order to request one image while acq in Live mode is already running # The acq should be automatically stopped and restarted, otherwise a RemoteEx error will be received. # error returned: ['7', 'AcqStart', 'async command pending', 'HAcq_mLive'] self.streakcam.StartAcquisition(self.readoutcam.acqMode) # acquire images def callback(dataflow, image): # self.streakcam.AcqAcqMonitor("Off") # TODO? self.readoutcam.data.unsubscribe(callback) # Note: MCPGain set to 0 is handled by stream not by driver except when changing from # "Operate" mode to "Focus" mode size = self.readoutcam.resolution.value self.assertEqual(image.shape, size[::-1]) # invert size self.assertIn(model.MD_EXP_TIME, image.metadata) logging.debug("Got image.") self.readoutcam.data.subscribe(callback) time.sleep(5)
def test_acq_wavelengthTable(self): """Get the scaling table (correction for mapping vertical px with timestamps) for the streak Time Range chosen for one sweep.""" # test a first value self.readoutcam.binning.value = (1, 1) self.streakunit.timeRange.value = util.find_closest(0.000000002, self.streakunit.timeRange.choices) # 2ns self.streakunit.streakMode.value = True # Note: RemoteEx automatically stops and restarts "Live" acq when changing settings img = self.readoutcam.data.get() wl_list_bin1 = img.metadata[model.MD_WL_LIST] self.assertIn(model.MD_TIME_LIST, img.metadata) self.assertIn(model.MD_WL_LIST, img.metadata) # check wavelength list changes when changing binning self.readoutcam.binning.value = (2, 2) img = self.readoutcam.data.get() wl_list_bin2 = img.metadata[model.MD_WL_LIST] self.assertEqual(len(wl_list_bin1)/2, len(wl_list_bin2)) with self.assertRaises(AssertionError): self.assertListEqual(wl_list_bin1, wl_list_bin2) # there is not assertListNotEqual...
def _on_em_view_mpp_change(self, mpp): """ Set the microscope's hfw when the MicroscopeView's mpp value changes The canvas calculates the new hfw value. """ # Only change the fov of the hardware if: # * this Viewport was *not* responsible for setting the mpp # (by calling `self.set_horizontal_field_width`) # * _and_ if it's displayed on screen (so we don't interfere with # viewports in other tabs that are not currently displayed) # This way, we prevent mpp/fov setting loops. if not self.self_set_mpp and self.IsShownOnScreen(): logging.debug("View mpp changed to %s on %s", mpp, self) hfw = self.get_fov_from_mpp() if hfw is None: return fov_va = self.microscope_view.fov_va try: # TODO: Test with a simulated SEM that has HFW choices choices = fov_va.choices # Get the choice that matches hfw most closely hfw = util.find_closest(hfw, choices) except NotApplicableError: hfw = fov_va.clip(hfw) # Indicate that this object was responsible for updating the hardware's HFW, so it won't # get updated again in `_on_hw_fov_change` self.self_set_fov = True logging.debug("Setting hardware FoV to %s", hfw) fov_va.value = hfw else: self.self_set_mpp = False
def test_acq_wavelengthTable(self): """Get the scaling table (correction for mapping vertical px with timestamps) for the streak Time Range chosen for one sweep.""" # test a first value self.readoutcam.binning.value = (1, 1) self.streakunit.timeRange.value = util.find_closest( 0.000000002, self.streakunit.timeRange.choices) # 2ns self.streakunit.streakMode.value = True # Note: RemoteEx automatically stops and restarts "Live" acq when changing settings img1 = self.readoutcam.data.get() wl_list_bin1 = img1.metadata[model.MD_WL_LIST] self.assertIn(model.MD_TIME_LIST, img1.metadata) self.assertIn(model.MD_WL_LIST, img1.metadata) # check wavelength list changes when changing binning self.readoutcam.binning.value = (2, 2) img2 = self.readoutcam.data.get() wl_list_bin2 = img2.metadata[model.MD_WL_LIST] self.assertEqual(len(wl_list_bin1) / 2, len(wl_list_bin2)) with self.assertRaises(AssertionError): self.assertListEqual( wl_list_bin1, wl_list_bin2) # there is not assertListNotEqual...
def set_attr(comp_name, attr_val_str): """ set the value of vigilant attribute of the given component. attr_val_str (dict str->str): attribute name -> value as a string """ component = get_component(comp_name) for attr_name, str_val in attr_val_str.items(): try: attr = getattr(component, attr_name) except Exception: raise ValueError("Failed to find attribute '%s' on component '%s'" % (attr_name, comp_name)) if not isinstance(attr, model.VigilantAttributeBase): raise ValueError("'%s' is not a vigilant attribute of component %s" % (attr_name, comp_name)) new_val = convert_to_object(str_val) # Special case for floats, due to rounding error, it's very hard to put the # exact value if it's an enumerated VA. So just pick the closest one in this # case. if isinstance(new_val, float) and ( hasattr(attr, "choices") and isinstance(attr.choices, collections.Iterable)): orig_val = new_val new_val = util.find_closest(new_val, attr.choices) if new_val != orig_val: logging.debug("Adjusting value to %s", new_val) try: attr.value = new_val except Exception as exc: raise IOError("Failed to set %s.%s = '%s': %s" % (comp_name, attr_name, str_val, exc))
def _onUpdateTriggerDelayMD(self, evt): """ Callback method for trigger delay ctrl GUI element. Overwrites the triggerDelay value in the MD after a new value was requested via the GUI. """ evt.Skip() cur_timeRange = self.streak_unit.timeRange.value requested_triggerDelay = self.ctrl_triggerDelay.GetValue() # get a copy of MD trigger2delay_MD = self.streak_delay.getMetadata()[model.MD_TIME_RANGE_TO_DELAY] # check if key already exists (prevent creating new key due to floating point issues) key = util.find_closest(cur_timeRange, trigger2delay_MD.keys()) if util.almost_equal(key, cur_timeRange): # Replace the current delay value with the requested for an already existing timeRange in the dict. # This avoid duplication of keys, which are only different because of floating point issues. trigger2delay_MD[key] = requested_triggerDelay else: trigger2delay_MD[cur_timeRange] = requested_triggerDelay logging.warning("A new entry %s was added to MD_TIME_RANGE_TO_DELAY, " "which is not in the device .timeRange choices.", cur_timeRange) # check the number of keys in the dict is same as choices for VA if len(trigger2delay_MD.keys()) != len(self.streak_unit.timeRange.choices): logging.warning("MD_TIME_RANGE_TO_DELAY has %d entries, while the device .timeRange has %d choices.", len(trigger2delay_MD.keys()), len(self.streak_unit.timeRange.choices)) self.streak_delay.updateMetadata({model.MD_TIME_RANGE_TO_DELAY: trigger2delay_MD}) # Note: updateMetadata should here never raise an exception as the UnitFloatCtrl already # catches errors regarding type and out-of-range inputs # update txt displayed in GUI self._onUpdateTriggerDelayGUI("Calibration not saved yet", odemis.gui.FG_COLOUR_WARNING)
def test_TimeRange(self): """Test time range VA for sweeping of streak unit.""" for timeRange in self.streakunit.timeRange.choices: # change timeRange VA to value in range self.streakunit.timeRange.value = util.find_closest(timeRange, self.streakunit.timeRange.choices) self.assertAlmostEqual(self.streakunit.timeRange.value, timeRange) tr2d = self.delaybox._metadata.get(model.MD_TIME_RANGE_TO_DELAY) if tr2d: key = util.find_closest(timeRange, tr2d.keys()) # check that the corresponding trigger delay is set when changing the .timeRange VA md_triggerDelay = tr2d[key] self.assertAlmostEqual(self.delaybox.triggerDelay.value, md_triggerDelay) # request value, which is not in choices of VA with self.assertRaises(IndexError): self.streakunit.timeRange.value = 0.000004 # 4us
def _setCCDFan(self, enable): """ Turn on/off the fan of the CCD enable (boolean): True to turn on/restore the fan, and False to turn if off """ if not self._has_fan_speed: return if self._fan_enabled == enable: return self._fan_enabled = enable comp = self._getComponent("ccd") if enable: if self._enabled_fan_speed is not None: logging.debug("Turning fan on of %s", comp.name) comp.fanSpeed.value = max(comp.fanSpeed.value, self._enabled_fan_speed) else: if comp.fanSpeed.value == 0: # Already off => don't touch it self._enabled_fan_speed = None self._enabled_fan_temp = None else: logging.debug("Turning fan off of %s", comp.name) self._enabled_fan_speed = comp.fanSpeed.value comp.fanSpeed.value = 0 # Raise targetTemperature to max/ambient to avoid the fan from # automatically starting again. (Some hardware have this built-in when # the current temperature is too high compared to the target) if self._has_fan_temp: temp = comp.targetTemperature if enable: if self._enabled_fan_temp is not None: temp.value = min(comp.targetTemperature.value, self._enabled_fan_temp) try: self._waitTemperatureReached(comp, timeout=60) except Exception as ex: logging.warning( "Failed to reach target temperature of CCD: %s", ex) else: # Set ~25°C == ambient temperature self._enabled_fan_temp = temp.value try: try: temp.value = min(comp.targetTemperature.range[1], 25) except AttributeError: temp.value = util.find_closest( 25, comp.targetTemperature.choices) except Exception: logging.warning( "Failed to change targetTemperature when disabling fan", exc_info=True)
def _setPower(self, value): powers = self.power.choices self._power = util.find_closest(value, powers) if self._power == 0: self.parent._device.HVBeamOff() else: self.parent._device.HVBeamOn() return self._power
def _doReference(self, axes): logging.debug("Referencing axis %s (-> %s)", self._axis, self._caxis) f = self._child.reference({self._caxis}) f.result() # If we just did homing and ended up to an unsupported position, move to # the nearest supported position cp = self._child.position.value[self._caxis] if (cp not in self._positions): nearest = util.find_closest(cp, self._positions.keys()) self._doMoveAbs({self._axis: nearest})
def _setCCDFan(self, enable): """ Turn on/off the fan of the CCD enable (boolean): True to turn on/restore the fan, and False to turn if off """ if not self._has_fan_speed: return if self._fan_enabled == enable: return self._fan_enabled = enable comp = self._getComponent("ccd") if enable: if self._enabled_fan_speed is not None: logging.debug("Turning fan on of %s", comp.name) comp.fanSpeed.value = max(comp.fanSpeed.value, self._enabled_fan_speed) else: if comp.fanSpeed.value == 0: # Already off => don't touch it self._enabled_fan_speed = None self._enabled_fan_temp = None else: logging.debug("Turning fan off of %s", comp.name) self._enabled_fan_speed = comp.fanSpeed.value comp.fanSpeed.value = 0 # Raise targetTemperature to max/ambient to avoid the fan from # automatically starting again. (Some hardware have this built-in when # the current temperature is too high compared to the target) if self._has_fan_temp: temp = comp.targetTemperature if enable: if self._enabled_fan_temp is not None: temp.value = min(comp.targetTemperature.value, self._enabled_fan_temp) try: self._waitTemperatureReached(comp, timeout=60) except Exception as ex: logging.warning("Failed to reach target temperature of CCD: %s", ex) else: # Set ~25°C == ambient temperature self._enabled_fan_temp = temp.value try: try: temp.value = min(comp.targetTemperature.range[1], 25) except (AttributeError, NotApplicableError): temp.value = util.find_closest(25, comp.targetTemperature.choices) except Exception: logging.warning("Failed to change targetTemperature when disabling fan", exc_info=True)
def _updatePosition(self): """ update the position VA """ # if it is an unsupported position report the nearest supported one real_pos = self._position[self._axis] nearest = util.find_closest(real_pos, self._positions.keys()) if not util.almost_equal(real_pos, nearest): logging.warning("Reporting axis %s @ %s (known position), while physical axis %s @ %s", self._axis, nearest, self._caxis, real_pos) pos = {self._axis: nearest} logging.debug("reporting position %s", pos) self.position._set_value(pos, force_write=True)
def _setPC(self, value): currents = self.probeCurrent.choices self._probeCurrent = util.find_closest(value, currents) self._indexCurrent = util.index_closest(value, self._list_currents) # Set the corresponding current index to Tescan SEM self.parent._device.SetPCIndex(self._indexCurrent + 1) # Adjust brightness and contrast with self.parent._acquisition_init_lock: self.parent._device.DtAutoSignal(self.parent._detector._channel) return self._probeCurrent
def get_time_range_to_trigger_delay(data, timeRange_choices, triggerDelay_range): """ Reads the time range and trigger delay values from a csv object. Checks values for validity. :parameter data: (csv.reader object) calibration file :parameter timeRange_choices: (frozenset) choices possible for timeRange VA :parameter triggerDelay_range: (tuple) range possible for trigger delay values :return: (dict) new dictionary containing the loaded time range to trigger delay info """ new_dict = {} for timeRange, delay in data: try: timeRange = float(timeRange) delay = float(delay) except ValueError: raise ValueError( "Trigger delay %s and/or time range %s is not of type float. " "Please check calibration file for trigger delay." % (delay, timeRange)) # check delay in range allowed if not triggerDelay_range[0] <= delay <= triggerDelay_range[1]: raise ValueError( "Trigger delay %s corresponding to time range %s is not in range %s. " "Please check the calibration file for the trigger delay." % (delay, timeRange, triggerDelay_range)) # check timeRange is in possible choices for timeRange on HW choice = find_closest(timeRange, timeRange_choices) if not almost_equal(timeRange, choice): raise ValueError( "Time range % s found in calibration file is not a possible choice " "for the time range of the streak unit. " "Please modify csv file so it fits the possible choices for the " "time range of the streak unit. " "Values in file must be of format timeRange:triggerDelay (per line)." % timeRange) new_dict[timeRange] = delay # check all time ranges are there if len(new_dict) == len(timeRange_choices): return new_dict else: raise ValueError( "The total number of %s time ranges in the loaded calibration file does not " "match the requested number of %s time ranges." % (len(new_dict), len(timeRange_choices)))
def read_trigger_delay_csv(filename, time_choices, trigger_delay_range): """ Read the MD_TIME_RANGE_TO_DELAY from a CSV file, and check its validity based on the hardware filename (str): the path to file time_choices (set): choices possible for timeRange VA trigger_delay_range (float, float): min/max value of the trigger delay return (dict float -> float): new dictionary containing the loaded time range to trigger delay info raise ValueError: if the data of the CSV file cannot be parsed or doesn't fit the hardware raise IOError: if the file doesn't exist """ tr2d = {} with open(filename, 'r', newline='') as csvfile: calibFile = csv.reader(csvfile, delimiter=':') for time_range, delay in calibFile: try: time_range = float(time_range) delay = float(delay) except ValueError: raise ValueError( "Trigger delay %s and/or time range %s is not of type float. " "Please check calibration file for trigger delay." % (delay, time_range)) # check delay in range allowed if not trigger_delay_range[0] <= delay <= trigger_delay_range[1]: raise ValueError( "Trigger delay %s corresponding to time range %s is not in range %s. " "Please check the calibration file for the trigger delay." % (delay, time_range, trigger_delay_range)) # check timeRange is in possible choices for timeRange on HW time_range_hw = find_closest(time_range, time_choices) if not almost_equal(time_range, time_range_hw): raise ValueError( "Time range % s found in calibration file is not a possible choice " "for the time range of the streak unit. " "Please modify CSV file so it fits the possible choices for the " "time range of the streak unit. " "Values in file must be of format timeRange:triggerDelay (per line)." % time_range) tr2d[time_range_hw] = delay # check all time ranges are there if len(tr2d) != len(time_choices): raise ValueError( "The total number of %s time ranges in the loaded calibration file does not " "match the requested number of %s time ranges." % (len(tr2d), len(time_choices))) return tr2d
def set_attr(comp_name, attr_name, str_val): """ set the value of vigilant attribute of the given component using the type of the current value of the attribute. """ try: component = get_component(comp_name) except LookupError: logging.error("Failed to find component '%s'", comp_name) return 127 except: logging.error("Failed to contact the back-end") return 127 try: attr = getattr(component, attr_name) except: logging.error("Failed to find attribute '%s' on component '%s'", attr_name, comp_name) return 129 if not isinstance(attr, model.VigilantAttributeBase): logging.error("'%s' is not a vigilant attribute of component '%s'", attr_name, comp_name) return 129 try: new_val = convertToObject(str_val) except Exception: return 127 # Special case for floats, due to rounding error, it's very hard to put the # exact value if it's an enumerated VA. So just pick the closest one in this # case. if isinstance(new_val, float) and (hasattr(attr, "choices") and isinstance( attr.choices, collections.Iterable)): orig_val = new_val new_val = util.find_closest(new_val, attr.choices) if new_val != orig_val: logging.debug("Adjusting value to %s", new_val) try: attr.value = new_val except: logging.exception("Failed to set %s.%s = '%s'", comp_name, attr_name, str_val) return 127 return 0
def set_attr(comp_name, attr_val_str): """ set the value of vigilant attribute of the given component. attr_val_str (dict str->str): attribute name -> value as a string """ component = get_component(comp_name) for attr_name, str_val in attr_val_str.items(): try: attr = getattr(component, attr_name) except Exception: raise ValueError( "Failed to find attribute '%s' on component '%s'" % (attr_name, comp_name)) if not isinstance(attr, model.VigilantAttributeBase): raise ValueError( "'%s' is not a vigilant attribute of component %s" % (attr_name, comp_name)) new_val = convert_to_object(str_val) # Special case for floats, due to rounding error, it's very hard to put the # exact value if it's an enumerated VA. So just pick the closest one in this # case. if (isinstance(new_val, float) and hasattr(attr, "choices") and isinstance(attr.choices, collections.Iterable)): orig_val = new_val choices = [ v for v in attr.choices if isinstance(v, numbers.Number) ] new_val = util.find_closest(new_val, choices) if new_val != orig_val: logging.debug("Adjusting value to %s", new_val) # Special case for None being referred to as "null" in YAML, but we should # also accept "None" elif new_val == "None" and not isinstance(attr.value, basestring): new_val = None logging.debug("Adjusting value to %s (null)", new_val) try: attr.value = new_val except Exception as exc: raise IOError("Failed to set %s.%s = '%s': %s" % (comp_name, attr_name, str_val, exc))
def _onOpenCalibFile(self, event): """ Loads a calibration file (*csv) containing the time range and the corresponding trigger delay for streak camera calibration. """ logging.debug( "Open trigger delay calibration file for temporal acquisition.") dialog = wx.FileDialog(self.panel, message="Choose a calibration file to load", defaultDir=self._calib_path, defaultFile="", style=wx.FD_OPEN, wildcard="csv files (*.csv)|*.csv") # Show the dialog and check whether is was accepted or cancelled if dialog.ShowModal() != wx.ID_OK: return # get selected path + filename and update default directory self._calib_path = dialog.GetDirectory() path = dialog.GetPath() filename = dialog.GetFilename() # read file try: tr2d = calibration.read_trigger_delay_csv( path, self.streak_unit.timeRange.choices, self.streak_delay.triggerDelay.range) except ValueError as error: self._onUpdateTriggerDelayGUI("Error while loading file!", odemis.gui.FG_COLOUR_HIGHLIGHT) logging.error("Failed loading %s: %s", filename, error) return # update the MD: overwrite the complete dict self.streak_delay.updateMetadata({model.MD_TIME_RANGE_TO_DELAY: tr2d}) # update triggerDelay shown in GUI cur_timeRange = self.streak_unit.timeRange.value # find the corresponding trigger delay key = util.find_closest(cur_timeRange, tr2d.keys()) # Note: no need to check almost_equal again as we do that already when loading the file self.streak_delay.triggerDelay.value = tr2d[key] # set the new value self._onUpdateTriggerDelayGUI(filename) # update txt displayed in GUI
def set_attr(comp_name, attr_name, str_val): """ set the value of vigilant attribute of the given component using the type of the current value of the attribute. """ try: component = get_component(comp_name) except LookupError: logging.error("Failed to find component '%s'", comp_name) return 127 except: logging.error("Failed to contact the back-end") return 127 try: attr = getattr(component, attr_name) except: logging.error("Failed to find attribute '%s' on component '%s'", attr_name, comp_name) return 129 if not isinstance(attr, model.VigilantAttributeBase): logging.error("'%s' is not a vigilant attribute of component '%s'", attr_name, comp_name) return 129 try: new_val = convertToObject(str_val) except Exception: return 127 # Special case for floats, due to rounding error, it's very hard to put the # exact value if it's an enumerated VA. So just pick the closest one in this # case. if isinstance(new_val, float) and ( hasattr(attr, "choices") and isinstance(attr.choices, collections.Iterable)): orig_val = new_val new_val = util.find_closest(new_val, attr.choices) if new_val != orig_val: logging.debug("Adjusting value to %s", new_val) try: attr.value = new_val except: logging.exception("Failed to set %s.%s = '%s'", comp_name, attr_name, str_val) return 127 return 0
def test_acqSync_EarlyEvents(self): """Test early events triggered in synchronous acquisition mode.""" # AsyncCommandStatus() returns: pending, preparing, active # AsyncCommandPreparing = True # action has not yet been started # AsyncCommandActive = True # action has been started # AsyncCommandPending = False # action has been ended, if True: action still going on # Note: async commands are: AcqStart, SeqStart, SeqSave, SeqLoad # choose very small exposure time to trigger for asynchronous commands are handled correctly self.readoutcam.exposureTime.value = 0.00001 # 10us self.streakunit.timeRange.value = util.find_closest( 0.000001, self.streakunit.timeRange.choices) size = self.readoutcam.resolution.value self.readoutcam.data.synchronizedOn(self.readoutcam.softwareTrigger) num_images = 5 self.camera_left = num_images def receive_image(dataflow, image): """Callback for readout camera""" self.assertEqual(image.shape, size[::-1]) # invert size self.assertIn(model.MD_EXP_TIME, image.metadata) self.camera_left -= 1 logging.debug("Got image.") if self.camera_left <= 0: dataflow.unsubscribe(receive_image) self.readoutcam.data.subscribe(receive_image) # Wait for the image for i in range(num_images): # call notify quickly to trigger an early event self.readoutcam.softwareTrigger.notify() if i == num_images - 1: time.sleep(0.01) # if trigger event was fast enough we should get more than one acq waiting in queue self.assertGreater(len(self.readoutcam.queue_events), 1) time.sleep(num_images * 0.2 * 2) # wait some time for acquisition to finish self.assertEqual(len(self.readoutcam.queue_events), 0)
def get_time_range_to_trigger_delay(data, timeRange_choices, triggerDelay_range): """ Reads the time range and trigger delay values from a csv object. Checks values for validity. :parameter data: (csv.reader object) calibration file :parameter timeRange_choices: (frozenset) choices possible for timeRange VA :parameter triggerDelay_range: (tuple) range possible for trigger delay values :return: (dict) new dictionary containing the loaded time range to trigger delay info """ new_dict = {} for timeRange, delay in data: try: timeRange = float(timeRange) delay = float(delay) except ValueError: raise ValueError("Trigger delay %s and/or time range %s is not of type float. " "Please check calibration file for trigger delay." % (delay, timeRange)) # check delay in range allowed if not triggerDelay_range[0] <= delay <= triggerDelay_range[1]: raise ValueError("Trigger delay %s corresponding to time range %s is not in range (0, 1). " "Please check the calibration file for the trigger delay." % (delay, timeRange)) # check timeRange is in possible choices for timeRange on HW choice = find_closest(timeRange, timeRange_choices) if not almost_equal(timeRange, choice): raise ValueError("Time range % s found in calibration file is not a possible choice " "for the time range of the streak unit. " "Please modify csv file so it fits the possible choices for the " "time range of the streak unit. " "Values in file must be of format timeRange:triggerDelay (per line)." % timeRange) new_dict[timeRange] = delay # check all time ranges are there if len(new_dict) == len(timeRange_choices): return new_dict else: raise ValueError("The total number of %s time ranges in the loaded calibration file does not " "match the requested number of %s time ranges." % (len(new_dict), len(timeRange_choices)))
def _on_em_view_mpp_change(self, mpp): """ Set the microscope's hfw when the MicroscopeView's mpp value changes (or the viewport size changes) The canvas calculates the new hfw value. """ fov = self.get_fov_from_mpp() self.microscope_view.fov.value = fov self.microscope_view.fov_buffer.value = self.get_buffer_fov_from_mpp() # Only change the FoV of the hardware if: # * this Viewport was *not* responsible for setting the mpp # (by calling `self.set_mpp_from_fov`) # * _and_ if it's displayed on screen (so we don't interfere with # viewports in other tabs that are not currently displayed) # This way, we prevent mpp/fov setting loops. if not self.self_set_mpp and self.IsShownOnScreen(): logging.debug("View mpp changed to %s on %s", mpp, self) if fov is None: return fov_va = self.microscope_view.fov_hw.horizontalFoV shape = self.microscope_view.fov_hw.shape # Compute the hfov, so that the whole HW FoV just fully fit hfov = min(fov[0], fov[1] * shape[0] / shape[1]) try: # TODO: Test with a simulated SEM that has HFW choices choices = fov_va.choices # Get the choice that matches hfw most closely hfov = util.find_closest(hfov, choices) except NotApplicableError: hfov = fov_va.clip(hfov) # Indicate that this object was responsible for updating the hardware's HFW, so it won't # get updated again in `_on_hw_fov_change` self.self_set_fov = True logging.debug("Setting hardware FoV to %s", hfov) fov_va.value = hfov else: self.self_set_mpp = False
def test_acqSync_EarlyEvents(self): """Test early events triggered in synchronous acquisition mode.""" # AsyncCommandStatus() returns: pending, preparing, active # AsyncCommandPreparing = True # action has not yet been started # AsyncCommandActive = True # action has been started # AsyncCommandPending = False # action has been ended, if True: action still going on # Note: async commands are: AcqStart, SeqStart, SeqSave, SeqLoad # choose very small exposure time to trigger for asynchronous commands are handled correctly self.readoutcam.exposureTime.value = 0.00001 # 10us self.streakunit.timeRange.value = util.find_closest(0.000001, self.streakunit.timeRange.choices) size = self.readoutcam.resolution.value self.readoutcam.data.synchronizedOn(self.readoutcam.softwareTrigger) num_images = 5 self.camera_left = num_images def receive_image(dataflow, image): """Callback for readout camera""" self.assertEqual(image.shape, size[::-1]) # invert size self.assertIn(model.MD_EXP_TIME, image.metadata) self.camera_left -= 1 logging.debug("Got image.") if self.camera_left <= 0: dataflow.unsubscribe(receive_image) self.readoutcam.data.subscribe(receive_image) # Wait for the image for i in range(num_images): # call notify quickly to trigger an early event self.readoutcam.softwareTrigger.notify() if i == num_images - 1: time.sleep(0.01) # if trigger event was fast enough we should get more than one acq waiting in queue self.assertGreater(len(self.readoutcam.queue_events), 1) time.sleep(num_images * 0.2 * 2) # wait some time for acquisition to finish self.assertEqual(len(self.readoutcam.queue_events), 0)
def _setTimeRange(self, value): """ Updates the timeRange VA. :parameter value: (float) value to be set :return: (float) current time range """ logging.debug("Reporting time range %s for streak unit.", value) self._metadata[model.MD_STREAK_TIMERANGE] = value # set corresponding trigger delay tr2d = self.parent._delaybox._metadata.get(model.MD_TIME_RANGE_TO_DELAY) if tr2d: key = util.find_closest(value, tr2d.keys()) if util.almost_equal(key, value): self.parent._delaybox.triggerDelay.value = tr2d[key] else: logging.warning("Time range %s is not a key in MD for time range to " "trigger delay calibration" % value) return value
def set_attr(comp_name, attr_val_str): """ set the value of vigilant attribute of the given component. attr_val_str (dict str->str): attribute name -> value as a string """ component = get_component(comp_name) for attr_name, str_val in attr_val_str.items(): try: attr = getattr(component, attr_name) except Exception: raise ValueError( "Failed to find attribute '%s' on component '%s'" % (attr_name, comp_name)) if not isinstance(attr, model.VigilantAttributeBase): raise ValueError( "'%s' is not a vigilant attribute of component %s" % (attr_name, comp_name)) new_val = convertToObject(str_val) # Special case for floats, due to rounding error, it's very hard to put the # exact value if it's an enumerated VA. So just pick the closest one in this # case. if isinstance(new_val, float) and (hasattr(attr, "choices") and isinstance( attr.choices, collections.Iterable)): orig_val = new_val new_val = util.find_closest(new_val, attr.choices) if new_val != orig_val: logging.debug("Adjusting value to %s", new_val) try: attr.value = new_val except Exception as exc: raise IOError("Failed to set %s.%s = '%s': %s" % (comp_name, attr_name, str_val, exc))
def test_acq_Live_RingBuffer_subscribe(self): """Acquire single image and receive it via the dataport.""" # Note: AcqStop can be called multiple times even if the acq is already stopped without causing an error # However, AcqStart can be only called once and raises an error if called while an acq is running. # AcqStop is not an asynchronous command. But it takes time until the status of the async command # "AcqStart" is properly finished. # Changing settings is not async. RemoteEx blocks as long as the settings are changed. # RemoteEx also stops the "Live" mode if a settings change is requested but does not restarts the "Live" mode. self.streakunit.streakMode.value = True self.streakunit.timeRange.value = util.find_closest( 0.000000002, self.streakunit.timeRange.choices) self.streakunit.MCPGain.value = 2 self.readoutcam.exposureTime.value = 0.1 # 100ms time.sleep(1) # start Live mode # and subscribe to dataflow afterwards in order to request one image while acq in Live mode is already running # The acq should be automatically stopped and restarted, otherwise a RemoteEx error will be received. # error returned: ['7', 'AcqStart', 'async command pending', 'HAcq_mLive'] self.streakcam.StartAcquisition( self.readoutcam.acqMode) # acquire images def callback(dataflow, image): # self.streakcam.AcqAcqMonitor("Off") # TODO? self.readoutcam.data.unsubscribe(callback) # Note: MCPGain set to 0 is handled by stream not by driver except when changing from # "Operate" mode to "Focus" mode size = self.readoutcam.resolution.value self.assertEqual(image.shape, size[::-1]) # invert size self.assertIn(model.MD_EXP_TIME, image.metadata) logging.debug("Got image.") self.readoutcam.data.subscribe(callback) time.sleep(5)
def _setWavelength(self, value): return find_closest(value, self._wl_px_values)
def test_acqSync_SingleLive_RingBuffer_subscribe(self): """Test to acquire synchronized images by subscribing.""" # test sync acq in focus mode self.streakunit.streakMode.value = False size = self.readoutcam.resolution.value # Note: When using self.acqMode = "SingleLive" parameters regarding the readout camera # need to be changed via location = "Live"! For now hardcoded in driver... self.readoutcam.exposureTime.value = 2 # s exp_time = self.readoutcam.exposureTime.value num_images = 5 self.images_left = num_images # unsubscribe after receiving number of images self.readoutcam.data.synchronizedOn(self.readoutcam.softwareTrigger) def receive_image(dataflow, image): """Callback for readout camera""" self.assertEqual(image.shape, size[::-1]) # invert size self.assertIn(model.MD_EXP_TIME, image.metadata) self.assertNotIn(model.MD_TIME_LIST, image.metadata) self.assertFalse(image.metadata[model.MD_STREAK_MODE]) self.images_left -= 1 logging.debug("Got image.") if self.images_left == 0: dataflow.unsubscribe(receive_image) self.assertEqual(self.streakunit.MCPGain.value, 0) # MCPGain should be zero when acq finished self.end_time = time.time() self.readoutcam.data.subscribe(receive_image) # Wait for the image for i in range(num_images): self.readoutcam.softwareTrigger.notify() time.sleep(i * 0.1) # wait a bit to simulate some processing # Waiting long enough time.sleep(num_images * exp_time + 2) self.assertEqual(self.images_left, 0) self.readoutcam.data.synchronizedOn(None) # check we can still get data normally img = self.readoutcam.data.get() # test sync acq in operate mode self.streakunit.streakMode.value = True self.streakunit.timeRange.value = util.find_closest(0.001, self.streakunit.timeRange.choices) num_images = 5 self.images_left = num_images # unsubscribe after receiving number of images self.readoutcam.data.synchronizedOn(self.readoutcam.softwareTrigger) def receive_image(dataflow, image): """Callback for readout camera""" self.assertEqual(image.shape, size[::-1]) # invert size self.assertIn(model.MD_EXP_TIME, image.metadata) self.assertIn(model.MD_TIME_LIST, image.metadata) self.assertTrue(image.metadata[model.MD_STREAK_MODE]) self.images_left -= 1 logging.debug("Got image.") if self.images_left == 0: dataflow.unsubscribe(receive_image) self.end_time = time.time() self.readoutcam.data.subscribe(receive_image) # Wait for the image for i in range(num_images): self.readoutcam.softwareTrigger.notify() time.sleep(i * 0.1) # wait a bit to simulate some processing # Waiting long enough time.sleep(num_images * exp_time + 2) self.assertEqual(self.images_left, 0) self.readoutcam.data.synchronizedOn(None) # check we can still get data normally img = self.readoutcam.data.get()
def _setTime(self, value): return find_closest(value, self._tl_px_values)
def move_abs(comp_name, moves, check_distance=True): """ move (in absolute) the axis of the given component to the specified position comp_name (str): name of the component check_distance (bool): if the axis is in meters, check that the move is not too big. moves (dict str -> str): axis -> position (as text) """ component = get_component(comp_name) act_mv = {} # axis -> value for axis_name, str_position in moves.items(): try: if axis_name not in component.axes: raise ValueError("Actuator %s has no axis '%s'" % (comp_name, axis_name)) ad = component.axes[axis_name] except (TypeError, AttributeError): raise ValueError("Component %s is not an actuator" % comp_name) # Allow the user to indicate the position via the user-friendly choice entry position = None if (hasattr(ad, "choices") and isinstance(ad.choices, dict)): for key, value in ad.choices.items(): if value == str_position: logging.info("Converting '%s' into %s", str_position, key) position = key # Even if it's a big distance, we don't complain as it's likely # that all choices are safe break if position is None: if ad.unit == "m": try: position = float(str_position) except ValueError: raise ValueError("Position '%s' cannot be converted to a number" % str_position) # compare to the current position, to see if the new position sounds reasonable cur_pos = component.position.value[axis_name] if check_distance and abs(cur_pos - position) > MAX_DISTANCE: raise IOError("Distance of %f m is too big (> %f m), use '--big-distance' to allow the move." % (abs(cur_pos - position), MAX_DISTANCE)) else: position = convert_to_object(str_position) # If only a couple of positions are possible, and asking for a float, # avoid the rounding error by looking for the closest possible if (isinstance(position, numbers.Real) and hasattr(ad, "choices") and isinstance(ad.choices, collections.Iterable) and position not in ad.choices): closest = util.find_closest(position, ad.choices) if util.almost_equal(closest, position, rtol=1e-3): logging.debug("Adjusting value %.15g to %.15g", position, closest) position = closest act_mv[axis_name] = position logging.info(u"Will move %s.%s to %s", comp_name, axis_name, units.readable_str(position, ad.unit, sig=3)) try: m = component.moveAbs(act_mv) try: m.result(120) except KeyboardInterrupt: logging.warning("Cancelling absolute move of component %s", comp_name) m.cancel() raise except Exception as exc: raise IOError("Failed to move component %s to %s: %s" % (comp_name, act_mv, exc))
def __init__(self, name, role, children, axis_name, positions, cycle=None, **kwargs): """ name (string) role (string) children (dict str -> actuator): axis name (in this actuator) -> actuator to be used for this axis axis_name (str): axis name in the child actuator positions (set or dict value -> str): positions where the actuator is allowed to move cycle (float): if not None, it means the actuator does a cyclic move and this value represents a full cycle """ # TODO: forbid inverted if len(children) != 1: raise ValueError("FixedPositionsActuator needs precisely one child") self._cycle = cycle self._move_sum = 0 self._position = {} self._referenced = {} axis, child = children.items()[0] self._axis = axis self._child = child self._caxis = axis_name self._positions = positions # Executor used to reference and move to nearest position self._executor = CancellableThreadPoolExecutor(max_workers=1) # one task at a time if not isinstance(child, model.ComponentBase): raise ValueError("Child %s is not a component." % (child,)) if not hasattr(child, "axes") or not isinstance(child.axes, dict): raise ValueError("Child %s is not an actuator." % child.name) if cycle is not None: # just an offset to reference switch position self._offset = self._cycle / len(self._positions) if not all(0 <= p < cycle for p in positions.keys()): raise ValueError("Positions must be between 0 and %s (non inclusive)" % (cycle,)) ac = child.axes[axis_name] axes = {axis: model.Axis(choices=positions, unit=ac.unit)} # TODO: allow the user to override the unit? model.Actuator.__init__(self, name, role, axes=axes, children=children, **kwargs) self._position = {} self.position = model.VigilantAttribute({}, readonly=True) logging.debug("Subscribing to position of child %s", child.name) child.position.subscribe(self._update_child_position, init=True) if model.hasVA(child, "referenced") and axis_name in child.referenced.value: self._referenced[axis] = child.referenced.value[axis_name] self.referenced = model.VigilantAttribute(self._referenced.copy(), readonly=True) child.referenced.subscribe(self._update_child_ref) # If the axis can be referenced => do it now (and move to a known position) # In case of cyclic move always reference if not self._referenced.get(axis, True) or (self._cycle and axis in self._referenced): # The initialisation will not fail if the referencing fails f = self.reference({axis}) f.add_done_callback(self._on_referenced) else: # If not at a known position => move to the closest known position nearest = util.find_closest(self._child.position.value[self._caxis], self._positions.keys()) self.moveAbs({self._axis: nearest}).result()
def move_abs(comp_name, moves, check_distance=True): """ move (in absolute) the axis of the given component to the specified position comp_name (str): name of the component check_distance (bool): if the axis is in meters, check that the move is not too big. moves (dict str -> str): axis -> position (as text) """ component = get_component(comp_name) act_mv = {} # axis -> value for axis_name, str_position in moves.items(): try: if axis_name not in component.axes: raise ValueError("Actuator %s has no axis '%s'" % (comp_name, axis_name)) ad = component.axes[axis_name] except (TypeError, AttributeError): raise ValueError("Component %s is not an actuator" % comp_name) # Allow the user to indicate the position via the user-friendly choice entry position = None if hasattr(ad, "choices") and isinstance(ad.choices, dict): for key, value in ad.choices.items(): if value == str_position: logging.info("Converting '%s' into %s", str_position, key) position = key # Even if it's a big distance, we don't complain as it's likely # that all choices are safe break if position is None: if ad.unit == "m": try: position = float(convert_to_object(str_position)) except ValueError: raise ValueError("Position '%s' cannot be converted to a number" % str_position) # compare to the current position, to see if the new position sounds reasonable cur_pos = component.position.value[axis_name] if check_distance and abs(cur_pos - position) > MAX_DISTANCE: raise IOError("Distance of %f m is too big (> %f m), use '--big-distance' to allow the move." % (abs(cur_pos - position), MAX_DISTANCE)) else: position = convert_to_object(str_position) # If only a couple of positions are possible, and asking for a float, # avoid the rounding error by looking for the closest possible if (isinstance(position, numbers.Real) and hasattr(ad, "choices") and isinstance(ad.choices, collections.Iterable) and position not in ad.choices): closest = util.find_closest(position, ad.choices) if util.almost_equal(closest, position, rtol=1e-3): logging.debug("Adjusting value %.15g to %.15g", position, closest) position = closest act_mv[axis_name] = position if isinstance(position, numbers.Real): pos_pretty = units.readable_str(position, ad.unit, sig=3) else: pos_pretty = "%s" % (position,) logging.info(u"Will move %s.%s to %s", comp_name, axis_name, pos_pretty) try: m = component.moveAbs(act_mv) try: m.result(120) except KeyboardInterrupt: logging.warning("Cancelling absolute move of component %s", comp_name) m.cancel() raise except Exception as exc: raise IOError("Failed to move component %s to %s: %s" % (comp_name, act_mv, exc))
def test_acqSync_SingleLive_RingBuffer_subscribe(self): """Test to acquire synchronized images by subscribing.""" # test sync acq in focus mode self.streakunit.streakMode.value = False size = self.readoutcam.resolution.value # Note: When using self.acqMode = "SingleLive" parameters regarding the readout camera # need to be changed via location = "Live"! For now hardcoded in driver... self.readoutcam.exposureTime.value = 2 # s exp_time = self.readoutcam.exposureTime.value num_images = 5 self.images_left = num_images # unsubscribe after receiving number of images self.readoutcam.data.synchronizedOn(self.readoutcam.softwareTrigger) def receive_image(dataflow, image): """Callback for readout camera""" self.assertEqual(image.shape, size[::-1]) # invert size self.assertIn(model.MD_EXP_TIME, image.metadata) self.assertNotIn(model.MD_TIME_LIST, image.metadata) self.assertFalse(image.metadata[model.MD_STREAK_MODE]) self.images_left -= 1 logging.debug("Got image.") if self.images_left == 0: dataflow.unsubscribe(receive_image) self.assertEqual(self.streakunit.MCPGain.value, 0) # MCPGain should be zero when acq finished self.end_time = time.time() self.readoutcam.data.subscribe(receive_image) # Wait for the image for i in range(num_images): self.readoutcam.softwareTrigger.notify() time.sleep(i * 0.1) # wait a bit to simulate some processing # Waiting long enough time.sleep(num_images * exp_time + 2) self.assertEqual(self.images_left, 0) self.readoutcam.data.synchronizedOn(None) # check we can still get data normally img = self.readoutcam.data.get() # test sync acq in operate mode self.streakunit.streakMode.value = True self.streakunit.timeRange.value = util.find_closest( 0.001, self.streakunit.timeRange.choices) num_images = 5 self.images_left = num_images # unsubscribe after receiving number of images self.readoutcam.data.synchronizedOn(self.readoutcam.softwareTrigger) def receive_image(dataflow, image): """Callback for readout camera""" self.assertEqual(image.shape, size[::-1]) # invert size self.assertIn(model.MD_EXP_TIME, image.metadata) self.assertIn(model.MD_TIME_LIST, image.metadata) self.assertTrue(image.metadata[model.MD_STREAK_MODE]) self.images_left -= 1 logging.debug("Got image.") if self.images_left == 0: dataflow.unsubscribe(receive_image) self.end_time = time.time() self.readoutcam.data.subscribe(receive_image) # Wait for the image for i in range(num_images): self.readoutcam.softwareTrigger.notify() time.sleep(i * 0.1) # wait a bit to simulate some processing # Waiting long enough time.sleep(num_images * exp_time + 2) self.assertEqual(self.images_left, 0) self.readoutcam.data.synchronizedOn(None) # check we can still get data normally img = self.readoutcam.data.get()