def __init__(self, name, role, parent, **kwargs): """ Note: parent should have a child "scanner" already initialised """ # It will set up ._shape and .parent model.Detector.__init__(self, name, role, parent=parent, **kwargs) self.data = SEMDataFlow(self, parent) self._acquisition_thread = None self._acquisition_lock = threading.Lock() self._acquisition_init_lock = threading.Lock() self._acquisition_must_stop = threading.Event() self.fake_img = self.parent.fake_img # The shape is just one point, the depth idt = numpy.iinfo(self.fake_img.dtype) data_depth = idt.max - idt.min + 1 self._shape = (data_depth,) # only one point self.drift_factor = 1 # dummy value for drift in pixels self.current_drift = 0 # Given that max resolution is half the shape of fake_img, # we set the drift bound to stay inside the fake_img bounds self.drift_bound = min(v // 4 for v in self.fake_img.shape[::-1]) self._update_drift_timer = util.RepeatingTimer(parent._drift_period, self._update_drift, "Drift update") if parent._drift_period: self._update_drift_timer.start()
def __init__(self, name, role, parent, **kwargs): """ axes (set of string): names of the axes """ self.parent = parent axes_def = { # Ranges are from the documentation "z": model.Axis(unit="m", range=(FOCUS_RANGE[0] * 1e-3, FOCUS_RANGE[1] * 1e-3)), } model.Actuator.__init__(self, name, role, parent=parent, axes=axes_def, **kwargs) # will take care of executing axis move asynchronously self._executor = CancellableThreadPoolExecutor(max_workers=1) # one task at a time # RO, as to modify it the client must use .moveRel() or .moveAbs() self.position = model.VigilantAttribute({}, unit="m", readonly=True) self._updatePosition() # Refresh regularly the position self._pos_poll = util.RepeatingTimer(5, self._refreshPosition, "Focus position polling") self._pos_poll.start()
def __init__(self, name, role, parent, **kwargs): """ axes (set of string): names of the axes """ fwd_info = parent.fwd_info() axes_def = { "z": model.Axis(unit=fwd_info["unit"], range=fwd_info["range"]), } model.Actuator.__init__(self, name, role, parent=parent, axes=axes_def, **kwargs) # will take care of executing axis move asynchronously self._executor = CancellableThreadPoolExecutor( max_workers=1) # one task at a time # RO, as to modify it the server must use .moveRel() or .moveAbs() self.position = model.VigilantAttribute({}, unit="m", readonly=True) self._updatePosition() # Refresh regularly the position self._pos_poll = util.RepeatingTimer(5, self._refreshPosition, "Position polling") self._pos_poll.start()
def __init__(self, node_idx, object_dict): super().__init__(node_idx, object_dict) self.tpdo = FakeTPDO(self) self.tpdo[1].map.append(self.sdo[POS_SDO][1]) self._tpdo_updater = util.RepeatingTimer(0.08, self._updateTPDO, "TPDO updater") self._tpdo_updater.start()
def start_generate(self): if self._generator is not None: logging.warning("Generator already running") return # Fixed sleep period of 1ms, and the acquisition is blocking on the dwellTime self._generator = util.RepeatingTimer(1e-3, self._generate, "Current reading") self._generator.start()
def _start_generate(self): if self._generator is not None: logging.warning("Generator already running") return self._generator = util.RepeatingTimer(self.exposureTime.value, self._generate, "SimCam image generator") self._generator.start()
def start_generate(self): if self._generator is not None: logging.warning("Generator already running") return self._generator = util.RepeatingTimer( 100e-3, # Fixed rate at 100ms self._generate, "Raw detector reading") self._generator.start()
def __init__(self, name, role, parent, hfw_nomag, **kwargs): model.Emitter.__init__(self, name, role, parent=parent, **kwargs) self.parent = parent # Distance between borders if magnification = 1. It should be found out # via calibration. self._hfw_nomag = hfw_nomag # m self.magnification = model.FloatContinuous( self.parent.GetMagnification(), unit="", readonly=True, range=MAGNIFICATION_RANGE) fov_range = (self._hfw_nomag / MAGNIFICATION_RANGE[1], self._hfw_nomag / MAGNIFICATION_RANGE[0]) self.horizontalFoV = model.FloatContinuous( self._hfw_nomag / self.magnification.value, range=fov_range, unit="m", setter=self._setHorizontalFoV) self.horizontalFoV.subscribe(self._onHorizontalFoV) self.blanker = model.VAEnumerated(self.parent.GetBlankBeam(), choices={True, False}, setter=self._setBlanker) self.external = model.VAEnumerated(self.parent.GetExternal(), choices={True, False}, setter=self._setExternal) #self.probeCurrent = model.FloatContinuous(1e-6, range=PC_RANGE, unit="A", # setter=self._setProbeCurrent) self.accelVoltage = model.FloatContinuous( 0, range=(VOLTAGE_RANGE[0] * 1e3, VOLTAGE_RANGE[1] * 1e3), unit="V", setter=self._setVoltage) # No pixelSize as there is no shape (not a full scanner) # To provide some rough idea of the step size when changing focus # Depends on the pixelSize, so will be updated whenever the HFW changes self.depthOfField = model.FloatContinuous(1e-6, range=(0, 1e3), unit="m", readonly=True) self._updateDepthOfField() # Refresh regularly the values, from the hardware, starting from now self._updateSettings() self._va_poll = util.RepeatingTimer(5, self._updateSettings, "Settings polling") self._va_poll.start()
def AVS_Measure(self, hdev, callback, nmsr): self._meas_nmsr = _val(nmsr) self._meas_tstart = time.time() self._meas_dur = self._meas_config.IntegrationTime / 1000 * self._meas_config.NrAverages self._ndata_read = 0 if cast(callback, c_void_p): # Not a null pointer self._meas_cb = callback self._meas_timer = util.RepeatingTimer(self._meas_dur, self._on_new_measurement, "Measurement callback thread") self._meas_timer.start() else: self._meas_cb = None
def start_generate(self): if self._generator is not None: logging.warning("Generator already running") return # In principle, toggling the shutter values here might interfere with the # shutter values set by the acquisition. However, in odemis, we never # do both things at the same time, so it is not an issue. if self._shutter_name: self.parent._toggle_shutters([self._shutter_name], True) self._generator = util.RepeatingTimer(100e-3, # Fixed rate at 100ms self._generate, "Raw detector reading") self._generator.start()
def __init__(self, name, role, positions, has_pressure=True, **kwargs): """ Initialises the component positions (list of str): each pressure positions supported by the component (among the allowed ones) has_pressure (boolean): if True, has a pressure VA with the current pressure. """ # TODO: or just provide .targetPressure (like .targetTemperature) ? # Or maybe provide .targetPosition: position that would be reached if # all the requested move were instantly applied? chp = {} for p in positions: try: chp[PRESSURES[p]] = p except KeyError: raise ValueError("Pressure position %s is unknown" % (p, )) axes = {"vacuum": model.Axis(unit="Pa", choices=chp)} model.Actuator.__init__(self, name, role, axes=axes, **kwargs) # For simulating moves self._position = PRESSURE_VENTED # last official position self._goal = PRESSURE_VENTED self._time_goal = 0 # time the goal was/will be reached self._time_start = 0 # time the move started # RO, as to modify it the client must use .moveRel() or .moveAbs() self.position = model.VigilantAttribute({"vacuum": self._position}, unit="Pa", readonly=True) if has_pressure: # Almost the same as position, but gives the current position self.pressure = model.VigilantAttribute(self._position, unit="Pa", readonly=True) self._press_timer = util.RepeatingTimer( 1, self._updatePressure, "Simulated pressure update") self._press_timer.start() else: self._press_timer = None # Indicates whether the chamber is opened or not # Just pretend it's always closed, and allow the user to change that # for instance via CLI. self.opened = model.BooleanVA(False) # will take care of executing axis move asynchronously self._executor = CancellableThreadPoolExecutor( max_workers=1) # one task at a time
def __init__(self, name, role, parent, rng=None, **kwargs): """ inverted (set of str): names of the axes which are inverted rng (dict str -> (float,float)): axis name -> min/max of the position on this axis. Note: if the axis is inverted, the range passed will be inverted. Also, if the hardware reports position outside of the range, move might fail, as it is considered outside of the range. """ if rng is None: rng = {} if "x" not in rng: rng["x"] = (5e-3, 152e-3) if "y" not in rng: rng["y"] = (5e-3, 152e-3) if "z" not in rng: rng["z"] = (5e-3, 40e-3) axes_def = { # Ranges are from the documentation "x": model.Axis(unit="m", range=(rng["x"][0], rng["x"][1])), "y": model.Axis(unit="m", range=(rng["y"][0], rng["y"][1])), "z": model.Axis(unit="m", range=(rng["z"][0], rng["z"][1])), } model.Actuator.__init__(self, name, role, parent=parent, axes=axes_def, **kwargs) # will take care of executing axis move asynchronously self._executor = CancellableThreadPoolExecutor( max_workers=1) # one task at a time # RO, as to modify it the client must use .moveRel() or .moveAbs() self.position = model.VigilantAttribute({}, unit="m", readonly=True) self._updatePosition() # Refresh regularly the position self._pos_poll = util.RepeatingTimer(5, self._refreshPosition, "Position polling") self._pos_poll.start()
def __init__(self, name, role, parent, **kwargs): """ Note: parent should have a child "scanner" already initialised """ # It will set up ._shape and .parent model.Detector.__init__(self, name, role, parent=parent, **kwargs) self.data = SEMDataFlow(self, parent) self._acquisition_thread = None self._acquisition_lock = threading.Lock() self._acquisition_init_lock = threading.Lock() self._acquisition_must_stop = threading.Event() self.fake_img = self.parent.fake_img # The shape is just one point, the depth idt = numpy.iinfo(self.fake_img.dtype) data_depth = idt.max - idt.min + 1 self._shape = (data_depth,) # only one point # 8 or 16 bits image if data_depth == 255: bpp = 8 else: bpp = 16 self.bpp = model.IntEnumerated(bpp, {8, 16}) # Simulate the Hw brightness/contrast, but don't actually do anything self.contrast = model.FloatContinuous(0.5, [0, 1], unit="") self.brightness = model.FloatContinuous(0.5, [0, 1], unit="") self.drift_factor = 2 # dummy value for drift in pixels self.current_drift = 0 # Given that max resolution is half the shape of fake_img, # we set the drift bound to stay inside the fake_img bounds self.drift_bound = min(v // 4 for v in self.fake_img.shape[::-1]) self._update_drift_timer = util.RepeatingTimer(parent._drift_period, self._update_drift, "Drift update") if parent._drift_period: self._update_drift_timer.start() # Special event to request software unblocking on the scan self.softwareTrigger = model.Event() self._metadata[model.MD_DET_TYPE] = model.MD_DT_NORMAL
def __init__(self, name, role, parent, rng=None, **kwargs): if rng is None: rng = {} stage_info = parent.stage_info() if "x" not in rng: rng["x"] = stage_info["range"]["x"] if "y" not in rng: rng["y"] = stage_info["range"]["y"] if "z" not in rng: rng["z"] = stage_info["range"]["z"] axes_def = { # Ranges are from the documentation "x": model.Axis(unit="m", range=rng["x"]), "y": model.Axis(unit="m", range=rng["y"]), "z": model.Axis(unit="m", range=rng["z"]), } model.Actuator.__init__(self, name, role, parent=parent, axes=axes_def, **kwargs) # will take care of executing axis move asynchronously self._executor = CancellableThreadPoolExecutor( max_workers=1) # one task at a time self.position = model.VigilantAttribute({}, unit=stage_info["unit"], readonly=True) self._updatePosition() # Refresh regularly the position self._pos_poll = util.RepeatingTimer(5, self._refreshPosition, "Position polling") self._pos_poll.start()
def __init__(self, name, role, parent, hfw_nomag, **kwargs): model.Emitter.__init__(self, name, role, parent=parent, **kwargs) self._hfw_nomag = hfw_nomag dwell_time_info = self.parent.dwell_time_info() self.dwellTime = model.FloatContinuous(self.parent.get_dwell_time(), dwell_time_info["range"], unit=dwell_time_info["unit"], setter=self._setDwellTime) voltage_info = self.parent.ht_voltage_info() self.accelVoltage = model.FloatContinuous(self.parent.get_ht_voltage(), voltage_info["range"], unit=voltage_info["unit"], setter=self._setVoltage) self.blanker = model.BooleanVA(self.parent.beam_is_blanked(), setter=self._setBlanker) spotsize_info = self.parent.spotsize_info() self.spotSize = model.FloatContinuous(self.parent.get_ebeam_spotsize(), spotsize_info["range"], unit=spotsize_info["unit"], setter=self._setSpotSize) beam_shift_info = self.parent.beam_shift_info() range_x = beam_shift_info["range"]["x"] range_y = beam_shift_info["range"]["y"] self.beamShift = model.TupleContinuous(self.parent.get_beam_shift(), ((range_x[0], range_y[0]), (range_x[1], range_y[1])), cls=(int, float), unit=beam_shift_info["unit"], setter=self._setBeamShift) rotation_info = self.parent.rotation_info() self.rotation = model.FloatContinuous(self.parent.get_rotation(), rotation_info["range"], unit=rotation_info["unit"], setter=self._setRotation) scanning_size_info = self.parent.scanning_size_info() fov = self.parent.get_scanning_size()[0] self.horizontalFoV = model.FloatContinuous( fov, unit=scanning_size_info["unit"], range=scanning_size_info["range"]["x"], setter=self._setHorizontalFoV) mag = self._hfw_nomag / fov mag_range_max = self._hfw_nomag / scanning_size_info["range"]["x"][0] mag_range_min = self._hfw_nomag / scanning_size_info["range"]["x"][1] self.magnification = model.FloatContinuous(mag, unit="", range=(mag_range_min, mag_range_max), readonly=True) # To provide some rough idea of the step size when changing focus # Depends on the pixelSize, so will be updated whenever the HFW changes self.depthOfField = model.FloatContinuous(1e-6, range=(0, 1e3), unit="m", readonly=True) self._updateDepthOfField() # Refresh regularly the values, from the hardware, starting from now self._updateSettings() self._va_poll = util.RepeatingTimer(5, self._updateSettings, "Settings polling") self._va_poll.start()
def __init__(self, name, role, port, axes, ustepsize, refproc=None, temp=False, **kwargs): """ port (str): port name (use /dev/fake for a simulator) axes (list of str): names of the axes, from the 1st to the 3rd. ustepsize (list of float): size of a microstep in m (the smaller, the bigger will be a move for a given distance in m) refproc (str or None): referencing (aka homing) procedure type. Use None to indicate it's not possible (no reference/limit switch) or the name of the procedure. For now only "2xFinalForward" is accepted. temp (bool): if True, will read the temperature from the analogue input (10 mV <-> 1 °C) inverted (set of str): names of the axes which are inverted (IOW, either empty or the name of the axis) """ if len(axes) != 3: raise ValueError("Axes must be a list of 3 axis names (got %s)" % (axes,)) self._axes_names = axes # axes names in order if len(axes) != len(ustepsize): raise ValueError("Expecting %d ustepsize (got %s)" % (len(axes), ustepsize)) if refproc not in {REFPROC_2XFF, REFPROC_FAKE, None}: raise ValueError("Reference procedure %s unknown" % (refproc, )) self._refproc = refproc for sz in ustepsize: if sz > 10e-3: # sz is typically ~1µm, so > 1 cm is very fishy raise ValueError("ustepsize should be in meter, but got %g" % (sz,)) self._ustepsize = ustepsize try: self._serial = self._openSerialPort(port) except serial.SerialException: raise HwError("Failed to find device %s on port %s. Ensure it is " "connected to the computer." % (name, port)) self._port = port self._ser_access = threading.Lock() self._target = 1 # Always one, when directly connected via USB self._resynchonise() modl, vmaj, vmin = self.GetVersion() if modl != 3110: logging.warning("Controller TMCM-%d is not supported, will try anyway", modl) if name is None and role is None: # For scan only return if port != "/dev/fake": # TODO: support programs in simulator # Detect if it is "USB bus powered" by using the fact that programs # don't run when USB bus powered addr = 80 # big enough to not overlap with REFPROC_2XFF programs prog = [(9, 50, 2, 1), # Set global param 50 to 1 (28,), # STOP ] self.UploadProgram(prog, addr) if not self._isFullyPowered(): # Only a warning, at the power can be connected afterwards logging.warning("Device %s has no power, the motor will not move", name) # will take care of executing axis move asynchronously self._executor = CancellableThreadPoolExecutor(max_workers=1) # one task at a time axes_def = {} for n, sz in zip(self._axes_names, self._ustepsize): # Mov abs supports ±2³¹ but the actual position is only within ±2²³ rng = [(-2 ** 23) * sz, (2 ** 23 - 1) * sz] # Probably not that much, but there is no info unless the axis has # limit switches and we run a referencing axes_def[n] = model.Axis(range=rng, unit="m") model.Actuator.__init__(self, name, role, axes=axes_def, **kwargs) for i, a in enumerate(self._axes_names): self._init_axis(i) driver_name = driver.getSerialDriver(self._port) self._swVersion = "%s (serial driver: %s)" % (odemis.__version__, driver_name) self._hwVersion = "TMCM-%d (firmware %d.%02d)" % (modl, vmaj, vmin) self.position = model.VigilantAttribute({}, unit="m", readonly=True) self._updatePosition() # TODO: add support for changing speed. cf p.68: axis param 4 + p.81 + TMC 429 p.6 self.speed = model.VigilantAttribute({}, unit="m/s", readonly=True) self._updateSpeed() if refproc is not None: # str -> boolean. Indicates whether an axis has already been referenced axes_ref = dict([(a, False) for a in axes]) self.referenced = model.VigilantAttribute(axes_ref, readonly=True) if temp: # One sensor is at the top, one at the bottom of the sample holder. # The most interesting is the temperature difference, so just # report both. self.temperature = model.FloatVA(0, unit=u"°C", readonly=True) self.temperature1 = model.FloatVA(0, unit=u"°C", readonly=True) self._temp_timer = util.RepeatingTimer(10, self._updateTemperatureVA, "TMCM temperature update") self._updateTemperatureVA() # make sure the temperature is correct self._temp_timer.start()
def __init__(self, name, role, image, children=None, daemon=None, **kwargs): ''' children (dict string->kwargs): parameters setting for the children. The only possible child is "focus". They will be provided back in the .children VA image (str or None): path to a file to use as fake image (relative to the directory of this class) ''' # TODO: support transpose? If not, warn that it's not accepted # fake image setup image = unicode(image) # change to this directory to ensure relative path is from this file os.chdir(os.path.dirname(unicode(__file__))) exporter = dataio.find_fittest_exporter(image) self._img = exporter.read_data(image)[0] # can be RGB or greyscale # we will fill the set of children with Components later in ._children model.DigitalCamera.__init__(self, name, role, daemon=daemon, **kwargs) if self._img.ndim > 3: # remove dims of length 1 self._img = numpy.squeeze(self._img) imshp = self._img.shape if len(imshp) == 3 and imshp[0] in {3, 4}: # CYX, change it to YXC, to simulate a RGB detector self._img = numpy.rollaxis(self._img, 2) # XCY self._img = numpy.rollaxis(self._img, 2) # YXC imshp = self._img.shape # For RGB, the colour is last dim, but we still indicate it as higher # dimension to ensure shape always starts with X, Y if len(imshp) == 3 and imshp[-1] in {3, 4}: # resolution doesn't affect RGB dim res = imshp[-2::-1] self._shape = res + imshp[-1::] # X, Y, C # indicate it's RGB pixel-per-pixel ordered self._img.metadata[model.MD_DIMS] = "YXC" else: res = imshp[::-1] self._shape = res # X, Y,... # TODO: handle non integer dtypes depth = 2**(self._img.dtype.itemsize * 8) self._shape += (depth, ) # TODO: don't provide range? or don't make it readonly? self.resolution = model.ResolutionVA(res, [res, res]) # , readonly=True) # TODO: support (simulated) binning self.binning = model.ResolutionVA((1, 1), [(1, 1), (1, 1)]) exp = self._img.metadata.get(model.MD_EXP_TIME, 0.1) # s self.exposureTime = model.FloatContinuous(exp, [1e-3, 1e3], unit="s") # Some code care about the readout rate to know how long an acquisition will take self.readoutRate = model.FloatVA(1e9, unit="Hz", readonly=True) pxs = self._img.metadata.get(model.MD_PIXEL_SIZE, (10e-6, 10e-6)) mag = self._img.metadata.get(model.MD_LENS_MAG, 1) spxs = tuple(s * mag for s in pxs) self.pixelSize = model.VigilantAttribute(spxs, unit="m", readonly=True) self._metadata = { model.MD_HW_NAME: "FakeCam", model.MD_SENSOR_PIXEL_SIZE: spxs } try: kwargs = children["focus"] except (KeyError, TypeError): logging.info("Will not simulate focus") self._focus = None else: self._focus = CamFocus(parent=self, daemon=daemon, **kwargs) self.children.value = self.children.value | {self._focus} # Simple implementation of the flow: we keep generating images and if # there are subscribers, they'll receive it. self.data = model.DataFlow(self) self._generator = util.RepeatingTimer(exp, self._generate, "SimCam image generator") self._generator.start()
def __init__(self, name, role, port, sources, _serial=None, **kwargs): """ port (string): name of the serial port to connect to. Can be a pattern, in which case, all the ports fitting the pattern will be tried, and the first one which looks like an LLE will be used. sources (dict string -> 5-tuple of float): the light sources (by colour). The string is one of the seven names for the sources: "red", "cyan", "green", "UV", "yellow", "blue", "teal". They correspond to fix number in the LLE (cf documentation). The tuple contains the wavelength in m for the 99% low, 25% low, centre/max, 25% high, 99% high. They do no have to be extremely precise. The most important is the centre, and that they are all increasing values. If the device doesn't have the source it can be skipped. _serial (serial): for internal use only, directly use a opened serial connection """ # start with this opening the port: if it fails, we are done if _serial is not None: self._try_recover = False self._serial = _serial self._port = "" else: self._serial, self._port = self._findDevice(port) logging.info("Found LLE device on port %s", self._port) self._try_recover = True # to acquire before sending anything on the serial port self._ser_access = threading.Lock() # Init the LLE self._initDevice() if _serial is not None: # used for port testing => only simple init return # parse source and do some sanity check if not sources or not isinstance(sources, dict): logging.error( "sources argument must be a dict of source name -> wavelength 5 points" ) raise ValueError("Incorrect sources argument") self._source_id = [] # source number for each spectra self._gy = [] # indexes of green and yellow source self._rcubt = [] # indexes of other sources spectra = [] # list of the 5 wavelength points for cn, wls in sources.items(): cn = cn.lower() if cn not in COLOUR_TO_SOURCE: raise ValueError( "Sources argument contains unknown colour '%s'" % cn) if len(wls) != 5: raise ValueError( "Sources colour '%s' doesn't have exactly 5 wavelength points" % cn) prev_wl = 0 for wl in wls: if 0 > wl or wl > 100e-6: raise ValueError( "Sources colour '%s' has unexpected wavelength = %f nm" % (cn, wl * 1e9)) if prev_wl > wl: raise ValueError( "Sources colour '%s' has unsorted wavelengths" % cn) self._source_id.append(COLOUR_TO_SOURCE[cn]) if cn in ["green", "yellow"]: self._gy.append(len(spectra)) else: self._rcubt.append(len(spectra)) spectra.append(tuple(wls)) model.Emitter.__init__(self, name, role, **kwargs) self._shape = () self._max_power = 0.4 # W (According to doc: ~400mW) self.power = model.FloatContinuous(0., (0., self._max_power), unit="W") # emissions is list of 0 <= floats <= 1. self._intensities = [0.] * len(spectra) # start off self.emissions = model.ListVA(list(self._intensities), unit="", setter=self._setEmissions) self.spectra = model.ListVA(spectra, unit="m", readonly=True) self._prev_intensities = [None] * len( spectra) # => will update for sure self._updateIntensities() # turn off every source self.power.subscribe(self._updatePower) # set HW and SW version self._swVersion = "%s (serial driver: %s)" % ( odemis.__version__, driver.getSerialDriver(self._port)) self._hwVersion = "Lumencor Light Engine" # hardware doesn't report any version # Update temperature every 10s current_temp = self.GetTemperature() self.temperature = model.FloatVA(current_temp, unit=u"°C", readonly=True) self._temp_timer = util.RepeatingTimer(10, self._updateTemperature, "LLE temperature update") self._temp_timer.start()