コード例 #1
0
    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()
コード例 #2
0
ファイル: zeiss.py プロジェクト: Mahmood-B/odemis
    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()
コード例 #3
0
    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()
コード例 #4
0
    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()
コード例 #5
0
ファイル: scpi.py プロジェクト: ihebdelmic/odemis
 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()
コード例 #6
0
 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()
コード例 #7
0
 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()
コード例 #8
0
    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()
コード例 #9
0
ファイル: avantes.py プロジェクト: lanery/odemis
    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
コード例 #10
0
 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()
コード例 #11
0
ファイル: simulated.py プロジェクト: effting/odemis
    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
コード例 #12
0
ファイル: zeiss.py プロジェクト: effting/odemis
    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()
コード例 #13
0
    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
コード例 #14
0
    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()
コード例 #15
0
    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()
コード例 #16
0
    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()
コード例 #17
0
    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()
コード例 #18
0
ファイル: lle.py プロジェクト: ihebdelmic/odemis
    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()