Beispiel #1
0
    def __init__(self, main):
        self.main = main

        # Streams available (handled by StreamController)
        # Note: we need to make sure ourselves that each stream in this
        # attribute is unique (i.e. only occurs once in the list).
        self.streams = model.ListVA()

        # Available Views. The are handled by the ViewController.
        # The `views` list basically keeps track of the relevant references.
        self.views = model.ListVA()

        # Current tool selected (from the toolbar, cf cont.tools)
        self.tool = None  # Needs to be overridden by a IntEnumerated

        # The MicroscopeView currently focused, it is one of the `views`
        # or `None`.
        self.focussedView = VigilantAttribute(None)

        layouts = set(
            [VIEW_LAYOUT_ONE, VIEW_LAYOUT_22, VIEW_LAYOUT_FULLSCREEN])
        self.viewLayout = model.IntEnumerated(VIEW_LAYOUT_22, choices=layouts)

        # The subset of views taken from `views` that *can* actually displayed,
        # but they might be hidden as well.
        # This attribute is also handled and manipulated by the ViewController.
        self.visible_views = model.ListVA()
Beispiel #2
0
    def __init__(self, name, role, **kwargs):
        model.Emitter.__init__(self, name, role, **kwargs)

        self._shape = ()
        self.power = model.FloatContinuous(0., [0., 100.], unit="W")
        self.power.subscribe(self._updatePower)
        # just one band: white
        # emissions is list of 0 <= floats <= 1. Always 1.0: cannot lower it.
        self.emissions = model.ListVA([1.0], unit="", setter=lambda x: [1.0])
        self.spectra = model.ListVA(
            [(380e-9, 160e-9, 560e-9, 960e-9, 740e-9)],
            unit="m",
            readonly=True)  # list of 5-tuples of floats
Beispiel #3
0
    def __init__(self, name, role, ports, **kwargs):
        """
        ports (string): pattern of the name of the serial ports to try to connect to
          find the devices. It can have a "glob", for example: "/dev/ttyUSB*"
        """
        model.Emitter.__init__(self, name, role, **kwargs)
        self._ports = ports
        self._master, self._devices = self._getAvailableDevices(ports)
        if not self._devices:
            raise HwError(
                "No Omicron xX device found for ports '%s', check "
                "that '%s' is turned on and connected to the computer." %
                (ports, name))

        spectra = [
        ]  # list of tuples: 99% low, 25% low, centre, 25% high, 99% high in m
        max_power = []  # list of float (W)
        for d in self._devices:
            spectra.append(d.wavelength)
            max_power.append(d.max_power)

        self._shape = ()

        # power of the whole device (=> max power of the device with max power)
        self.power = model.FloatContinuous(0., (0., max(max_power)), unit="W")
        self.power.subscribe(self._updatePower)

        # ratio of power per device
        # => if some device don't support max power, clamped before 1
        self.emissions = model.ListVA([0.] * len(self._devices),
                                      unit="",
                                      setter=self._setEmissions)
        # info on what device is which wavelength
        self.spectra = model.ListVA(spectra, unit="m", readonly=True)

        # Ensure the whole Hub is turned on
        if self._master:
            try:
                self._master.PowerOn()
            except OXXError:
                raise HwError(
                    "Failed to power on the master device, check the interlock."
                )

        # make sure everything is off (turning on the HUB will turn on the lights)
        self._updateIntensities(self.power.value, self.emissions.value)

        # set SW version
        driver_name = self._devices[0].acc.driver
        self._swVersion = "%s (serial driver: %s)" % (odemis.__version__,
                                                      driver_name)
Beispiel #4
0
 def __init__(self, name, x, y, z, milling_angle, streams=None):
     """
     :param name: (string) the feature name
     :param x: (float) the X axis of the feature position
     :param y: (float) the Y axis of the feature position
     :param z: (float) the Z axis of the feature position
     :param milling_angle: (float)  angle used for milling (angle between the sample and the ion-beam, similar to the
     one in the chamber tab, not the actual Rx)
     :param streams: (List of StaticStream) list of acquired streams on this feature
     """
     self.name = model.StringVA(name)
     # The 3D position of an interesting point in the site (Typically, the milling should happen around that
     # volume, never touching it.)
     self.pos = model.TupleContinuous(
         (x, y, z),
         range=((-1, -1, -1), (1, 1, 1)),
         cls=(int, float),
     )
     # TODO: Check if negative milling angle is allowed
     if milling_angle <= 0:
         milling_angle = DEFAULT_MILLING_ANGLE
         logging.warning(
             f"Given milling angle {milling_angle} is negative, setting it to default {DEFAULT_MILLING_ANGLE}"
         )
     self.milling_angle = model.FloatVA(milling_angle)
     self.status = model.StringVA(FEATURE_ACTIVE)
     # TODO: Handle acquired files
     self.streams = streams if streams is not None else model.ListVA()
Beispiel #5
0
    def test_reproduceTypedValue_good(self):
        """
        check various inputs and compare to expected output
        for values that should work
        """
        lva = model.ListVA([12, -3])
        # example value / input str / expected output
        tc = [(3, "-1561", -1561),
              (-9.3, "0.123", 0.123),
              (False, "true", True),
              ({"a": 12.5, "b": 3.}, "c:6,d:1.3", {"c": 6., "d":1.3}),
              ((-5, 0, 6), " 9, -8", (9, -8)), # we don't force to be the same size
              ((1.2, 0.0), "0, -8, -15e-3, 6.", (0.0, -8.0, -15e-3, 6.0)),
              ([1.2, 0.0], "0.1", [0.1]),
              (("cou", "bafd"), "aa,bb", ("aa", "bb")),
              # more complicated but nice to support for the user
              ((1200, 256), "256 x 256 px", (256, 256)),
              ((1.2, 256), " 21 x 0.2 m", (21, 0.2)),
              ([-5, 0, 6], "9,, -8", [9, -8]),
              ((1.2, 0.0), "", tuple()),
              (lva.value, "-1, 63, 12", [-1, 63, 12]), #NotifyingList becomes a list
              ((-5, 0, 6), "9.3, -8", (9, 3, -8)), # maybe this shouldn't work?
              # Note: we don't support SI prefixes
              (("cou",), "aa, c a", ("aa", " c a")), # TODO: need to see if spaces should be kept or trimmed
              ]

        for ex_val, str_val, expo in tc:
            out = reproduceTypedValue(ex_val, str_val)
            self.assertEqual(out, expo,
                 "Testing with %s / '%s' -> %s" % (ex_val, str_val, out))
Beispiel #6
0
    def __init__(self, main):
        assert main.microscope is not None
        MicroscopyGUIData.__init__(self, main)

        # Current tool selected (from the toolbar)
        tools = set([TOOL_NONE, TOOL_ZOOM, TOOL_ROI])
        self.tool = IntEnumerated(TOOL_NONE, choices=tools)

        # Represent the global state of the microscopes. Mostly indicating
        # whether optical/sem streams are active.
        hw_states = {STATE_OFF, STATE_ON, STATE_DISABLED}

        if self.main.ccd:
            self.opticalState = model.IntEnumerated(STATE_OFF,
                                                    choices=hw_states)

        if self.main.ebeam:
            self.emState = model.IntEnumerated(STATE_OFF, choices=hw_states)

        # history list of visited stage positions, ordered with latest visited
        # as last entry.
        self.stage_history = model.ListVA()

        # VA for autofocus procedure mode
        self.autofocus_active = BooleanVA(False)
Beispiel #7
0
    def __init__(self, name, image):
        """
        Note: parameters are different from the base class.
        image (DataArray of shape (111)YX): raw data. The metadata should
          contain at least MD_POS and MD_PIXEL_SIZE. It should also contain
          MD_IN_WL and MD_OUT_WL.
        """
        # Wavelengths
        try:
            exc_range = image.metadata[model.MD_IN_WL]
            self.excitation = VigilantAttribute(exc_range,
                                                unit="m",
                                                readonly=True)
        except KeyError:
            logging.warning("No excitation wavelength for fluorescence stream")

        try:
            em_range = image.metadata[model.MD_OUT_WL]
            self.emission = VigilantAttribute(em_range,
                                              unit="m",
                                              readonly=True)

            default_tint = conversion.wave2rgb(numpy.mean(em_range))
        except KeyError:
            logging.warning("No emission wavelength for fluorescence stream")
            default_tint = (0, 255, 0)  # green is most typical

        # colouration of the image
        tint = image.metadata.get(model.MD_USER_TINT, default_tint)
        self.tint = model.ListVA(tint, unit="RGB")  # 3-tuple R,G,B
        self.tint.subscribe(self.onTint)

        # Do it at the end, as it forces it the update of the image
        StaticStream.__init__(self, name, image)
Beispiel #8
0
    def __init__(self, name, role, **kwargs):
        # TODO: allow the user to indicate the power and the spectrum via args?
        # This will create the .powerSupply VA
        model.Emitter.__init__(self, name, role, **kwargs)
        self.powerSupply.value = False  # immediately turn it off

        self._shape = ()
        self.power = model.FloatContinuous(0., (0., 10.), unit="W")
        self.power.subscribe(self._updatePower)
        # just one band: white
        # In general emissions is list of 0 <= floats <= 1. Here it's just one value, either 0 or 1.
        self.emissions = model.ListVA([0], unit="", setter=self._setEmissions)
        self.emissions.subscribe(self._updatePower)
        # TODO: update spectra VA to support the actual spectra of the lamp
        self.spectra = model.ListVA([(380e-9, 390e-9, 560e-9, 730e-9, 740e-9)],
                                    unit="m",
                                    readonly=True)
Beispiel #9
0
    def __init__(self,
                 name,
                 role,
                 sn=None,
                 max_power=0.1,
                 inversed=False,
                 **kwargs):
        """
        sn (None or str): serial number.
           If None, it will pick the first device found.
        max_power (0<float): maxium power emitted in W.
        """
        model.Emitter.__init__(self, name, role, **kwargs)

        self._sn = sn
        self._max_power = max_power

        # Just find the first BlinkStick led controller
        if sn is None:
            self._bstick = blinkstick.find_first()
        else:
            # Note: doesn't work with v1.1.7:
            # need fix on get_string(), reported here: https://github.com/arvydas/blinkstick-python/pull/35
            logging.warning(
                "Using sn to select the device doesn't currently work")
            self._bstick = blinkstick.find_by_serial(sn)
        if self._bstick is None:
            raise HwError(
                "Failed to find a Blinkstick for component %s. "
                "Check that the device is connected to the computer." %
                (name, ))

        self._bstick.set_inverse(inversed)
        time.sleep(0.1)  # Device apparently needs some time to recover

        self._shape = ()
        # list of 5-tuples of floats
        self.spectra = model.ListVA([(380e-9, 390e-9, 560e-9, 730e-9, 740e-9)],
                                    unit="m",
                                    readonly=True)

        self.power = model.ListContinuous([
            0.,
        ], ((0., ), (max_power, )),
                                          unit="W",
                                          cls=(int, long, float),
                                          setter=self._setPower)
        self.power.subscribe(self._updatePower, init=True)

        self._swVersion = "Blinkstick v%s" % (blinkstick.__version__, )
        # These functions report wrong values on Linux with v1.1.7
        #         man = self._bstick.get_manufacturer()
        #         desc = self._bstick.get_description()
        #         rsn = self._bstick.get_serial()
        man = self._bstick.device.manufacturer
        desc = self._bstick.device.product
        rsn = self._bstick.device.serial_number
        self._hwVersion = "%s %s (s/n: %s)" % (man, desc, rsn)
Beispiel #10
0
    def __init__(self, name, role, dependencies, **kwargs):
        """
        dependencies (dict str -> Emitter): arbitrary role -> emitter to be used as
          part of this emitter. All its provided emissions will be provided.
        """
        # TODO: allow to only use a subset of the emissions from each child

        if not dependencies:
            raise ValueError("MultiplexLight needs dependencies")

        model.Emitter.__init__(self,
                               name,
                               role,
                               dependencies=dependencies,
                               **kwargs)
        self._shape = ()

        self._child_idx = {
        }  # Emitter -> index (shift) in the emissions/spectra

        spectra = []
        for n, child in dependencies.items():
            if not (model.hasVA(child, "power") and model.hasVA(
                    child, "emissions") and model.hasVA(child, "spectra")):
                raise ValueError("Child %s is not a light emitter" % (n, ))
            self._child_idx[child] = len(spectra)
            spectra.extend(child.spectra.value)
            # TODO: update emissions whenever the child emissions change

        # Child with the maximum power range
        max_power = max(c.power.range[1] for c in self.dependencies.value)
        self.power = model.FloatContinuous(0, (0., max_power), unit="W")
        self.power.subscribe(self._updatePower)

        # info on which source is which wavelength
        self.spectra = model.ListVA(spectra, unit="m", readonly=True)

        # It needs .spectra and .power
        pwr, em = self._readPwrEmissions()
        self.power._value = pwr

        # ratio of power per source
        # if some source don't support max power, clamped before 1
        self.emissions = model.ListVA(em, unit="", setter=self._setEmissions)
Beispiel #11
0
    def __init__(self, name, role, ports, **kwargs):
        """
        ports (string): pattern of the name of the serial ports to try to connect to
          find the devices. It can have a "glob", for example: "/dev/ttyUSB*"
        """
        model.Emitter.__init__(self, name, role, **kwargs)
        self._ports = ports
        self._devices = self._getAvailableDevices(ports)
        if not self._devices:
            raise HwError("No Omicron xX device found for ports '%s', check "
                          "they are turned on and connected to the computer."
                          % ports)
        
        spectra = [] # list of tuples: 99% low, 25% low, centre, 25% high, 99% high in m
        max_power = [] # list of float (W)
        for d in self._devices:
            wl = d.wavelength
            # Lasers => spectrum is almost just one wl, but make it 2 nm wide
            # to avoid a bandwidth of exactly 0.
            spectra.append((wl - 1e-9, wl - 0.5e-9, wl, wl + 0.5e-9, wl + 1e-9))
            max_power.append(d.max_power)

        self._shape = ()

        # power of the whole device (=> max power of the device with max power)
        self.power = model.FloatContinuous(0., (0., max(max_power)), unit="W")
        self.power.subscribe(self._updatePower)

        # ratio of power per device
        # => if some device don't support max power, clamped before 1
        self.emissions = model.ListVA([0.] * len(self._devices), unit="", 
                                      setter=self._setEmissions)
        # info on what device is which wavelength 
        self.spectra = model.ListVA(spectra, unit="m", readonly=True)
        
        # make sure everything is off
        self._updateIntensities(self.power.value, self.emissions.value)
        
        # set HW and SW version
        driver_name = driver.getSerialDriver(self._devices[0].port)
        self._swVersion = "%s (serial driver: %s)" % (odemis.__version__, driver_name)
        self._hwVersion = "Omicron xX" # TODO: get version from GetFirmware()
Beispiel #12
0
    def __init__(self, name, role, max_power=10.0, spectra=None, **kwargs):
        """
        max_power (0 < float): the maximum power (in W)
        spectra (list of list of 5 tuple): output spectrum, as 5 wavelengths in m
        """
        model.Emitter.__init__(self, name, role, **kwargs)

        self._shape = ()
        self.power = model.FloatContinuous(0., (0., max_power), unit="W")
        self.power.subscribe(self._updatePower)
        # just one band: white
        # emissions is list of 0 <= floats <= 1. Always 1.0: cannot lower it.
        self.emissions = model.ListVA([1.0], unit="", setter=lambda x: [1.0])

        # list of 5-tuples of floats
        if spectra is None:
            spectra = [(380e-9, 390e-9, 560e-9, 730e-9, 740e-9)] # White
        if len(spectra) != 1 or len(spectra[0]) != 5:
            raise ValueError("spectra argument must be a list of list of 5 values")
        self.spectra = model.ListVA([tuple(spectra[0])], unit="m", readonly=True)
Beispiel #13
0
    def __init__(self, name, role, **kwargs):
        # TODO: allow the user to indicate the power and the spectrum via args?
        # This will create the .powerSupply VA
        model.Emitter.__init__(self, name, role, **kwargs)
        self.powerSupply.value = False  # immediately turn it off

        self._shape = ()
        self.power = model.ListContinuous([0], ((0,), (10,)), unit="W", cls=(int, long, float),
                                          setter=self._setPower)
        # just one band: white
        # TODO: update spectra VA to support the actual spectra of the lamp
        self.spectra = model.ListVA([(380e-9, 390e-9, 560e-9, 730e-9, 740e-9)],
                                    unit="m", readonly=True)
Beispiel #14
0
    def duplicate_tab_data_model(self, orig):
        """
        Duplicate a MicroscopyGUIData and adapt it for the acquisition window
        The streams will be shared, but not the views
        orig (MicroscopyGUIData)
        return (MicroscopyGUIData)
        """
        # TODO: we'd better create a new view and copy the streams
        new = copy.copy(orig)  # shallow copy

        new.streams = model.ListVA(orig.streams.value)  # duplicate

        # create view (which cannot move or focus)
        view = guimodel.MicroscopeView("All")

        # differentiate it (only one view)
        new.views = model.ListVA()
        new.views.value.append(view)
        new.focussedView = model.VigilantAttribute(view)
        new.viewLayout = model.IntEnumerated(guimodel.VIEW_LAYOUT_ONE,
                                             choices={guimodel.VIEW_LAYOUT_ONE})
        new.tool = model.IntEnumerated(TOOL_NONE, choices={TOOL_NONE})
        return new
Beispiel #15
0
    def create_cryo_tab_model(self):
        main = gmodel.MainGUIData(None)  # no microscope backend
        # add role, features and currentFeature directly
        main.role = "cryo"
        main.features = omodel.ListVA()
        main.currentFeature = omodel.VigilantAttribute(None)
        tab = gmodel.CryoGUIData(main)

        # Add one view
        fview = gmodel.MicroscopeView("fakeview")
        tab.views.value.append(fview)
        tab.focussedView.value = fview

        return tab
Beispiel #16
0
    def test_history_overlay(self):
        cnvs = miccanvas.DblMicroscopeCanvas(self.panel)
        cnvs.disable_drag()
        self.add_control(cnvs, wx.EXPAND, proportion=1, clear=True)
        history_va = omodel.ListVA()

        hol = vol.HistoryOverlay(cnvs, history_va)
        cnvs.add_view_overlay(hol)

        test.gui_loop()

        for j in range(4):
            offset = ((j - 2) * 75)

            for i in range(10):
                history_va.value.append(
                    (((j * i) + offset, (j * -i) + offset), None)
                )

            for i in range(10):
                history_va.value.append(
                    (((j * -i) + offset, (j * -i) + offset), None)
                )

            for i in range(10):
                history_va.value.append(
                    (((j * -i) + offset, (j * i) + offset), None)
                )

            for i in range(10):
                history_va.value.append(
                    (((j * i) + offset, (j * i) + offset), None)
                )

        test.gui_loop()

        steps = 1000
        step_size = 10
        for i in xrange(0, steps * step_size, step_size):
            phi = (math.pi * 2) / steps
            x = (100 * i / (steps * 5)) * math.cos(i * phi)
            y = (100 * i / (steps * 5)) * math.sin(i * phi)
            # hol.history.append(((x, y), None))
            history_va.value.append(
                ((x, y), None)
            )
        # print "Done generating history"

        test.gui_loop()
Beispiel #17
0
    def test_pretty_str(self):
        prop = model.IntVA(2)
        pretty_str = str(prop)
        self.assertIn("IntVA", pretty_str)
        self.assertIn(str(prop.value), pretty_str)

        prop = model.ListVA([2, 3], unit=u"µm")
        pretty_str = str(prop)
        self.assertIn("ListVA", pretty_str)
        self.assertIn(str(prop.value), pretty_str)

        prop = model.FloatContinuous(2.3, unit=u"µm", range=(1.0, 9))
        pretty_str = str(prop)
        self.assertIn("FloatContinuous", pretty_str)
        self.assertIn(str(prop.value), pretty_str)
Beispiel #18
0
    def __init__(self, main):
        assert main.microscope is not None
        MicroscopyGUIData.__init__(self, main)

        # check there is something to move
        if not main.microscope.actuators:
            raise KeyError("No actuators found in the microscope")

        # Step size name -> val, range, actuator, axes (None if all)
        # str -> float, [float, float], str, (str, ...)
        ss_def = {
            "stage": (1e-6, [1e-8, 1e-3], "stage", None),
            "focus": (1e-7, [1e-8, 1e-4], "focus", None),
            "aligner": (1e-6, [1e-8, 1e-4], "aligner", None),
            # Mirror is a bit more complicated as it has 4 axes and Y
            # usually needs to be 10x bigger than X
            "mirror_x": (1e-6, [1e-8, 1e-3], "mirror", ("x", )),
            "mirror_y": (10e-6, [1e-8, 1e-3], "mirror", ("y", )),
            "mirror_r": (10e-6, [1e-8, 1e-3], "mirror", ("ry", "rz"))
        }
        # str -> VA: name (as the name of the attribute) -> step size (m)
        self.stepsizes = {}

        # This allow the UI code to mention axes only as role/axis name.
        # str -> (str, str):
        # role/axis ("mirror/x") -> (actuator ("mirror"), stepsize ("mirror_r"))
        self._axis_to_act_ss = {}

        # remove the ones that don't have an actuator
        for ss, (v, r, an, axn) in ss_def.items():
            if getattr(main, an) is not None:
                self.stepsizes[ss] = model.FloatContinuous(v, r)
                if axn is None:
                    axn = getattr(main, an).axes
                for a in axn:
                    self._axis_to_act_ss[(an, a)] = (an, ss)
                    logging.debug("Add axis %s/%s to stepsize %s", an, a, ss)

        # set of (str, str): actuator name, axis name
        self.axes = frozenset(self._axis_to_act_ss.keys())

        # Tools are for lens alignment (mirror alignment actually needs none)
        tools = set([TOOL_NONE, TOOL_DICHO, TOOL_SPOT])
        self.tool = IntEnumerated(TOOL_NONE, choices=tools)

        # For dichotomic mode
        self.dicho_seq = model.ListVA()  # list of 4 enumerated for each corner
Beispiel #19
0
    def __init__(self, name, role, max_power=10.0, spectra=None, **kwargs):
        """
        max_power (0 < float): the maximum power (in W)
        spectra (list of list of 5 tuple): output spectrum, as 5 wavelengths in m
        """
        model.Emitter.__init__(self, name, role, **kwargs)

        self._shape = ()
        self.power = model.ListContinuous([0], ((0,), (max_power,)), unit="W", cls=(int, long, float),
                                          setter=self._setPower)
        # just one band: white
        # list of 5-tuples of floats
        if spectra is None:
            spectra = [(380e-9, 390e-9, 560e-9, 730e-9, 740e-9)] # White
        if len(spectra) != 1 or len(spectra[0]) != 5:
            raise ValueError("spectra argument must be a list of list of 5 values")
        self.spectra = model.ListVA([tuple(spectra[0])], unit="m", readonly=True)
Beispiel #20
0
    def __init__(self, name, daemon):
        model.Component.__init__(self, name=name, daemon=daemon)
        self.executor = futures.ThreadPoolExecutor(max_workers=1)
        self.number_futures = 0
        self.startAcquire = model.Event() # triggers when the acquisition of .data starts
        self.data = FakeDataFlow(sae=self.startAcquire)
        self.datas = SynchronizableDataFlow()

        self.data_count = 0
        self._df = None

        # TODO automatically register the property when serializing the Component
        self.prop = model.IntVA(42)
        self.cont = model.FloatContinuous(2.0, [-1, 3.4], unit="C")
        self.enum = model.StringEnumerated("a", {"a", "c", "bfds"})
        self.cut = model.IntVA(0, setter=self._setCut)
        self.listval = model.ListVA([2, 65])
Beispiel #21
0
    def test_dichotomy_overlay(self):
        cnvs = miccanvas.SecomCanvas(self.panel)
        self.add_control(cnvs, wx.EXPAND, proportion=1, clear=True)

        lva = omodel.ListVA()

        dol = vol.DichotomyOverlay(cnvs, lva)
        cnvs.add_view_overlay(dol)

        def do_stuff(value):
            """ Test function that can be used to subscribe to VAs """
            print "Testing VA subscriber received value ", value

        self.dummy = do_stuff
        dol.sequence_va.subscribe(do_stuff, init=True)
        dol.sequence_va.value = [0, 1, 2, 3, 0]
        dol.activate()

        test.gui_loop()
Beispiel #22
0
    def __init__(self, name, role, dependencies, **kwargs):
        """
        dependencies (dict str -> Emitter): arbitrary role -> emitter to be used as
          part of this emitter. All its provided emissions will be provided.
        """
        # TODO: allow to only use a subset of the emissions from each child

        if not dependencies:
            raise ValueError("MultiplexLight needs dependencies")

        model.Emitter.__init__(self, name, role, dependencies=dependencies, **kwargs)
        self._shape = ()

        self._child_idx = {} # Emitter -> index (shift) in the power/spectra

        spectra = []
        min_power = []
        max_power = []
        for n, child in dependencies.items():
            if not (model.hasVA(child, "power") and
                    model.hasVA(child, "spectra")
                   ):
                raise ValueError("Child %s is not a light emitter" % (n,))
            self._child_idx[child] = len(spectra)
            spectra.extend(child.spectra.value)
            min_power.extend(child.power.range[0])
            max_power.extend(child.power.range[1])
            # Subscribe to each child power to update self.power
            child.power.subscribe(self._updateMultiplexPower)

        # Child with the maximum power range
        self.power = model.ListContinuous(value=[0] * len(spectra),
                                          range=(tuple(min_power), tuple(max_power)),
                                          unit="W", cls=(int, long, float))
        self.power.subscribe(self._setChildPower)
        self._updateMultiplexPower(None)
        # info on which source is which wavelength
        self.spectra = model.ListVA(spectra, unit="m", readonly=True)
Beispiel #23
0
    def __init__(self, name, detector, dataflow, emitter, focuser=None, opm=None,
                 hwdetvas=None, hwemtvas=None, detvas=None, emtvas=None, raw=None,
                 acq_type=None):
        """
        name (string): user-friendly name of this stream
        detector (Detector): the detector which has the dataflow
        dataflow (Dataflow): the dataflow from which to get the data
        emitter (Emitter): the emitter
        opm (OpticalPathManager): the optical path manager
        focuser (Actuator or None): an actuator with a 'z' axis that allows to change
          the focus
        hwdetvas (None or set of str): names of all detector hardware VAs to be controlled by this
            Stream
        hwemtvas (None or set of str): names of all emitter hardware VAs to be controlled by this
            Stream
        detvas (None or set of str): names of all the detector VigilantAttributes
          (VAs) to be duplicated on the stream. They will be named .detOriginalName
        emtvas (None or set of str): names of all the emitter VAs to be
          duplicated on the stream. They will be named .emtOriginalName
        raw (None or list of DataArrays or DataArrayShadow): raw data to be used
          at initialisation. By default, it will contain no data.
        acq_type (MD_AT_*): acquisition type associated with this stream (as in model._metadata)
        """
        self.name = model.StringVA(name)
        self.acquisitionType = model.VigilantAttribute(acq_type)  # MD_ACQ_TYPE or None
        # for identification of the acquisition type associated with the stream

        # Hardware Components
        self._detector = detector
        self._emitter = emitter
        self._focuser = focuser
        self._opm = opm

        # Dataflow (Live image stream with meta data)
        # Note: A Detector can have multiple dataflows, so that's why a Stream
        # has a separate attribute.
        self._dataflow = dataflow

        # TODO: We need to reorganise everything so that the
        # image display is done via a dataflow (in a separate thread), instead
        # of a VA.
        self._im_needs_recompute = threading.Event()
        self._init_thread()

        # list of DataArray(Shadow) received and used to generate the image
        # every time it's modified, image is also modified
        if raw is None:
            self.raw = []
        else:
            self.raw = raw

        # initialize the projected tiles cache
        self._projectedTilesCache = {}
        # initialize the raw tiles cache
        self._rawTilesCache = {}

        # TODO: should better be based on a BufferedDataFlow: subscribing starts
        # acquisition and sends (raw) data to whoever is interested. .get()
        # returns the previous or next image acquired.

        # indicating if stream has already been prepared
        self._prepared = False
        # TODO: should_update is a GUI stuff => move away from stream
        # should_update has no effect direct effect, it's just a flag to
        # indicate the user would like to have the stream updated (live)
        self.should_update = model.BooleanVA(False)
        # is_active set to True will keep the acquisition going on
        self.is_active = model.BooleanVA(False, setter=self._is_active_setter)

        # Leech to use during acquisition.
        # Note: for now only some streams actually use them (MDStreams*)
        self.leeches = []

        # Hardware VA that the stream is directly linked to
        self.hw_vas = {}
        self.hw_vas.update(self._getVAs(detector, hwdetvas or set()))
        self.hw_vas.update(self._getVAs(emitter, hwemtvas or set()))

        # Duplicate VA if requested
        self._hwvas = {}  # str (name of the proxied VA) -> original Hw VA
        self._hwvasetters = {}  # str (name of the proxied VA) -> setter
        self._lvaupdaters = {}  # str (name of the proxied VA) -> listener

        self._det_vas = self._duplicateVAs(detector, "det", detvas or set())
        self._emt_vas = self._duplicateVAs(emitter, "emt", emtvas or set())

        self._dRangeLock = threading.Lock()
        self._drange = None  # min/max data range, or None if unknown
        self._drange_unreliable = True  # if current values are a rough guess (based on detector)

        # drange_raw is the smaller (less zoomed) image of an pyramidal image. It is used
        # instead of the full image because it would be too slow or even impossible to read
        # the full data from the image to the memory. It is also not the tiles from the tiled
        # image, so the code for pyramidal and non-pyramidal images
        # that reads drange_raw is the same.
        # The drawback of not using the full image, is that some of the pixels are lost, so
        # maybe the max/min of the smaller image is different from the min/max of the full image.
        # And the histogram of both images will probably be a bit different also.
        if raw and isinstance(raw[0], model.DataArrayShadow):
            # if the image is pyramidal, use the smaller image
            drange_raw = self._getMergedRawImage(raw[0], raw[0].maxzoom)
        else:
            drange_raw = None

        # TODO: move to the DataProjection class
        self.auto_bc = model.BooleanVA(True)
        self.auto_bc.subscribe(self._onAutoBC)

        # % of values considered outliers discarded in auto BC detection
        # Note: 1/256th is a nice value because on RGB, it means in degenerated
        # cases (like flat histogram), you still loose only one value on each
        # side.
        self.auto_bc_outliers = model.FloatContinuous(100 / 256, range=(0, 40))
        self.auto_bc_outliers.subscribe(self._onOutliers)

        self.tint = model.ListVA((255, 255, 255), unit="RGB")  # 3-int R,G,B

        # Used if auto_bc is False
        # min/max ratio of the whole intensity level which are mapped to
        # black/white. Its range is ._drange (will be updated by _updateDRange)
        self.intensityRange = model.TupleContinuous((0, 0),
                                                    range=((0, 0), (1, 1)),
                                                    cls=(int, long, float),
                                                    setter=self._setIntensityRange)
        # Make it so that the value gets clipped when its range is updated and
        # the value is outside of it.
        self.intensityRange.clip_on_range = True
        self._updateDRange(drange_raw)  # sets intensityRange
        self._init_projection_vas()

        # Histogram of the current image _or_ slightly older image.
        # Note it's an ndarray. Use .tolist() to get a python list.
        self.histogram = model.VigilantAttribute(numpy.empty(0), readonly=True)
        self.histogram._full_hist = numpy.ndarray(0) # for finding the outliers
        self.histogram._edges = None

        # Tuple of (int, str) or (None, None): loglevel and message
        self.status = model.VigilantAttribute((None, None), readonly=True)

        # Background data, to be subtracted from the acquisition data before
        # projection. It should be the same shape and dtype as the acquisition
        # data, otherwise no subtraction will be performed. If None, nothing is
        # subtracted is applied.
        self.background = model.VigilantAttribute(None, setter=self._setBackground)
        self.background.subscribe(self._onBackground)

        # if there is already some data, update image with it
        # TODO: have this done by the child class, if needed.
        if self.raw:
            self._updateHistogram(drange_raw)
            self._onNewData(None, self.raw[0])
Beispiel #24
0
    def test_list(self):
        prop = model.ListVA([2.0, 5, 4])

        self.called = 0
        # now count
        prop.subscribe(self.callback_test_notify, init=True)  # +1

        # List assignment
        prop.value = [3.0, 5]  # +1
        prop.value = list((0, ))  # +1
        prop.value = [0]  # nothing because same value

        # Item removal
        del prop.value[0]  # +1
        self.assertEqual(prop.value, [])

        prop.value = [1, 2, 3, 4]  # +1

        del prop.value[1:-1]  # +1
        self.assertEqual(prop.value, [1, 4])

        # Insert and remove item
        prop.value.insert(1, 66)  # +1
        self.assertEqual(prop.value, [1, 66, 4])
        prop.value.remove(66)  # +1
        self.assertEqual(prop.value, [1, 4])

        # Item adding
        prop.value += [44]  # +1
        self.assertEqual(prop.value, [1, 4, 44])
        self.assertEqual(self.called, 9, "Called has value %s" % self.called)

        prop.value.extend([43, 42])  # +1
        prop.value.extend(
            [])  # The list value stays the same, so no increase!!
        self.assertEqual(prop.value, [1, 4, 44, 43, 42])

        prop.value.append(41)  # +1
        self.assertEqual(prop.value, [1, 4, 44, 43, 42, 41])

        # In place repetition
        orig_len = len(prop.value)
        prop.value *= 3  # +1
        self.assertEqual(len(prop.value), orig_len * 3)
        self.assertEqual(self.called, 12, "Called has value %s" % self.called)

        # Item assignment
        prop.value = list(range(5))  # +1
        prop.value[1] = 5  # +1
        prop.value[1] = 5  # +0
        self.assertEqual(prop.value, [0, 5, 2, 3, 4])
        self.assertEqual(prop.value.pop(), 4)  # +1

        # Item sorting
        prop.value.sort()  # +1
        self.assertEqual(prop.value, [0, 2, 3, 5])
        prop.value.reverse()  # +1
        self.assertEqual(prop.value, [5, 3, 2, 0])

        # pl = pickle.dumps(prop.value, pickle.HIGHEST_PROTOCOL)
        # unpl = pickle.loads(pl)
        # self.assertEqual(unpl, prop.value)

        try:
            prop.value = 45
            self.fail("Assigning int to a list should not be allowed.")
        except TypeError:
            pass  # as it should be
        prop.unsubscribe(self.callback_test_notify)
        self.assertEqual(self.called, 17, "Called has value %s" % self.called)

        prop.value = ["b"]  # no more counting

        self.assertEqual(prop.value, ["b"])
        self.assertEqual(self.called, 17, "Called has value %s" % self.called)
Beispiel #25
0
    def __init__(self,
                 name,
                 stage=None,
                 focus0=None,
                 focus1=None,
                 stream_classes=None):
        """
        :param name (string): user-friendly name of the view
        :param stage (Actuator): actuator with two axes: x and y
        :param focus0 (Actuator): actuator with one axis: z. Can be None
        :param focus1 (Actuator): actuator with one axis: z. Can be None
          Focuses 0 and 1 are modified when changing focus respectively along
          the X and Y axis.
        :param stream_classes (None, or tuple of classes): all subclasses that the
          streams in this view is allowed to show.
        """

        super(MicroscopeView, self).__init__(name)

        if stream_classes is None:
            self.stream_classes = (Stream, )
        else:
            self.stream_classes = stream_classes
        self._stage = stage
        self._focus = [focus0, focus1]

        # The real stage position, to be modified via moveStageToView()
        # it's a direct access from the stage, so looks like a dict of axes
        if stage:
            self.stage_pos = stage.position
            # stage.position.subscribe(self.onStagePos)

            # the current center of the view, which might be different from
            # the stage
            # TODO: we might need to have it on the MicroscopeModel, if all the
            # viewports must display the same location
            pos = self.stage_pos.value
            view_pos_init = (pos["x"], pos["y"])
        else:
            view_pos_init = (0, 0)

        self.view_pos = model.ListVA(view_pos_init, unit="m")

        # current density (meter per pixel, ~ scale/zoom level)
        # 10µm/px => ~large view of the sample
        self.mpp = FloatContinuous(10e-6, range=(10e-12, 1e-3), unit="m/px")

        # How much one image is displayed on the other one. Value used by
        # StreamTree
        self.merge_ratio = FloatContinuous(0.3, range=[0, 1], unit="")
        self.merge_ratio.subscribe(self._onMergeRatio)

        # Streams to display (can be considered an implementation detail in most
        # cases)
        # Note: use addStream/removeStream for simple modifications
        self.stream_tree = StreamTree(merge=self.merge_ratio.value)
        # Only modify with this lock acquired:
        # TODO: Is this the source of the intermittent locking of the GUI when
        # Streams are active? If so, is there another/better way?
        self._streams_lock = threading.Lock()

        # TODO: list of annotations to display
        self.show_crosshair = model.BooleanVA(True)
Beispiel #26
0
    def __init__(self, name, image, *args, **kwargs):
        """
        name (string)
        image (model.DataArray(Shadow) of shape (CYX), (C11YX), (CTYX), (CT1YX), (1T1YX)).
        The metadata MD_WL_POLYNOMIAL or MD_WL_LIST should be included in order to
        associate the C to a wavelength.
        The metadata MD_TIME_LIST should be included to associate the T to a timestamp

        .background is a DataArray of shape (CT111), where C & T have the same length as in the data.
        .efficiencyCompensation is always DataArray of shape C1111.

        """
        # Spectrum stream has in addition to normal stream:
        #  * information about the current bandwidth displayed (avg. spectrum)
        #  * coordinates of 1st point (1-point, line)
        #  * coordinates of 2nd point (line)

        # TODO: need to handle DAS properly, in case it's tiled (in XY), to avoid
        # loading too much data in memory.
        # Ensure the data is a DataArray, as we don't handle (yet) DAS
        if isinstance(image, model.DataArrayShadow):
            image = image.getData()

        if len(image.shape) == 3:
            # force 5D for CYX
            image = image[:, numpy.newaxis, numpy.newaxis, :, :]
        elif len(image.shape) == 4:
            # force 5D for CTYX
            image = image[:, :, numpy.newaxis, :, :]
        elif len(image.shape) != 5 or image.shape[2] != 1:
            logging.error("Cannot handle data of shape %s", image.shape)
            raise NotImplementedError(
                "StaticSpectrumStream needs 3D or 4D data")

        # This is for "average spectrum" projection
        # cached list of wavelength for each pixel pos
        self._wl_px_values, unit_bw = spectrum.get_spectrum_range(image)
        min_bw, max_bw = self._wl_px_values[0], self._wl_px_values[-1]
        cwl = (max_bw + min_bw) / 2
        width = (max_bw - min_bw) / 12

        # The selected wavelength for a temporal spectrum display
        self.selected_wavelength = model.FloatContinuous(
            self._wl_px_values[0],
            range=(min_bw, max_bw),
            unit=unit_bw,
            setter=self._setWavelength)

        # Is there time data?
        if image.shape[1] > 1:
            # cached list of timestamps for each position in the time dimension
            self._tl_px_values, unit_t = spectrum.get_time_range(image)
            min_t, max_t = self._tl_px_values[0], self._tl_px_values[-1]

            # Allow the select the time as any value within the range, and the
            # setter will automatically "snap" it to the closest existing timestamp
            self.selected_time = model.FloatContinuous(self._tl_px_values[0],
                                                       range=(min_t, max_t),
                                                       unit=unit_t,
                                                       setter=self._setTime)

        # This attribute is used to keep track of any selected pixel within the
        # data for the display of a spectrum
        self.selected_pixel = model.TupleVA((None, None))  # int, int

        # first point, second point in pixels. It must be 2 elements long.
        self.selected_line = model.ListVA([(None, None), (None, None)],
                                          setter=self._setLine)

        # The thickness of a point or a line (shared).
        # A point of width W leads to the average value between all the pixels
        # which are within W/2 from the center of the point.
        # A line of width W leads to a 1D spectrum taking into account all the
        # pixels which fit on an orthogonal line to the selected line at a
        # distance <= W/2.
        self.selectionWidth = model.IntContinuous(1, [1, 50], unit="px")
        self.selectionWidth.subscribe(self._onSelectionWidth)

        # Peak method index, None if spectrum peak fitting curve is not displayed
        self.peak_method = model.VAEnumerated("gaussian",
                                              {"gaussian", "lorentzian", None})

        # TODO: allow to pass the calibration data as argument to avoid
        # recomputing the data just after init?
        # Spectrum efficiency compensation data: None or a DataArray (cf acq.calibration)
        self.efficiencyCompensation = model.VigilantAttribute(
            None, setter=self._setEffComp)
        self.efficiencyCompensation.subscribe(self._onCalib)

        # Is there spectrum data?
        if image.shape[0] > 1:
            # low/high values of the spectrum displayed
            self.spectrumBandwidth = model.TupleContinuous(
                (cwl - width, cwl + width),
                range=((min_bw, min_bw), (max_bw, max_bw)),
                unit=unit_bw,
                cls=(int, long, float))
            self.spectrumBandwidth.subscribe(self.onSpectrumBandwidth)

            # Whether the (per bandwidth) display should be split intro 3 sub-bands
            # which are applied to RGB
            self.fitToRGB = model.BooleanVA(False)
            self.fitToRGB.subscribe(self.onFitToRGB)

        # the raw data after calibration
        self.calibrated = model.VigilantAttribute(image)

        if "acq_type" not in kwargs:
            if image.shape[0] > 1 and image.shape[1] > 1:
                kwargs["acq_type"] = model.MD_AT_TEMPSPECTRUM
            elif image.shape[0] > 1:
                kwargs["acq_type"] = model.MD_AT_SPECTRUM
            elif image.shape[1] > 1:
                kwargs["acq_type"] = model.MD_AT_TEMPORAL
            else:
                logging.warning(
                    "SpectrumStream data has no spectrum or time dimension, shape = %s",
                    image.shape)

        super(StaticSpectrumStream, self).__init__(name, [image], *args,
                                                   **kwargs)

        # Automatically select point/line if data is small (can only be done
        # after .raw is set)
        if image.shape[-2:] == (1,
                                1):  # Only one point => select it immediately
            self.selected_pixel.value = (0, 0)
        elif image.shape[
                -2] == 1:  # Horizontal line => select line immediately
            self.selected_line.value = [(0, 0), (image.shape[-1] - 1, 0)]
        elif image.shape[-1] == 1:  # Vertical line => select line immediately
            self.selected_line.value = [(0, 0), (0, image.shape[-2] - 1)]
Beispiel #27
0
    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()
Beispiel #28
0
    def __init__(self, name, detector, dataflow, emitter):
        """
        name (string): user-friendly name of this stream
        detector (Detector): the detector which has the dataflow
        dataflow (Dataflow): the dataflow from which to get the data
        emitter (Emitter): the emitter
        """

        self.name = model.StringVA(name)

        # Hardware Components
        self._detector = detector
        self._emitter = emitter

        # Dataflow (Live image stream with meta data)
        # Note: A Detectors can have multiple dataflows, so that's why a Stream
        # has a separate attribute.
        self._dataflow = dataflow

        # TODO: this flag is horrendous as it can lead to not updating the image
        # with the latest image. We need to reorganise everything so that the
        # image display is done via a dataflow (in a separate thread), instead
        # of a VA.
        self._running_upd_img = False  # to avoid simultaneous updates in different threads
        # list of DataArray received and used to generate the image
        # every time it's modified, image is also modified
        self.raw = []
        # the most important attribute
        self.image = model.VigilantAttribute(None)

        # TODO: should maybe to 2 methods activate/deactivate to explicitly
        # start/stop acquisition, and one VA "updated" to stated that the user
        # want this stream updated (as often as possible while other streams are
        # also updated)
        # should_update has no effect direct effect, it's just a flag to
        # indicate the user would like to have the stream updated (live)
        self.should_update = model.BooleanVA(False)
        # is_active set to True will keep the acquisition going on
        self.is_active = model.BooleanVA(False)
        self.is_active.subscribe(self.onActive)

        # Region of interest as left, top, right, bottom (in ratio from the
        # whole area of the emitter => between 0 and 1)
        self.roi = model.TupleContinuous((0, 0, 1, 1),
                                         range=((0, 0, 0, 0), (1, 1, 1, 1)),
                                         cls=(int, long, float))

        self._drange = None  # min/max data range, or None if unknown

        # whether to use auto brightness & contrast
        self.auto_bc = model.BooleanVA(True)
        # % of values considered outliers discarded in auto BC detection
        # Note: 1/256th is a nice value because on RGB, it means in degenerated
        # cases (like flat histogram), you still loose only one value on each
        # side.
        self.auto_bc_outliers = model.FloatContinuous(100 / 256, range=(0, 40))

        # Used if auto_bc is False
        # min/max ratio of the whole intensity level which are mapped to
        # black/white. Its range is ._drange (will be updated by _updateDRange)
        self.intensityRange = model.TupleContinuous(
            (0, 0),
            range=((0, 0), (1, 1)),
            cls=(int, long, float),
            setter=self._setIntensityRange)
        # Make it so that the value gets clipped when its range is updated and
        # the value is outside of it.
        self.intensityRange.clip_on_range = True
        self._updateDRange()

        # Histogram of the current image _or_ slightly older image.
        # Note it's an ndarray. Use .tolist() to get a python list.
        self.histogram = model.VigilantAttribute(numpy.empty(0), readonly=True)
        self.histogram._full_hist = numpy.ndarray(
            0)  # for finding the outliers
        self.histogram._edges = None

        self.auto_bc.subscribe(self._onAutoBC)
        self.auto_bc_outliers.subscribe(self._onOutliers)
        self.intensityRange.subscribe(self._onIntensityRange)
        self._ht_needs_recompute = threading.Event()
        self._hthread = threading.Thread(target=self._histogram_thread,
                                         name="Histogram computation")
        self._hthread.daemon = True
        self._hthread.start()

        # self.histogram.subscribe(self._onHistogram) # FIXME -> update outliers and then image

        # list of warnings to display to the user
        # TODO should be a set
        self.warnings = model.ListVA([])  # should only contain WARNING_*
Beispiel #29
0
    def __init__(self, name, image):
        """
        name (string)
        image (model.DataArray of shape (CYX) or (C11YX)). The metadata
        MD_WL_POLYNOMIAL should be included in order to associate the C to a
        wavelength.
        """
        self._calibrated = None  # just for the _updateDRange to not complain
        Stream.__init__(self, name, None, None, None)
        # Spectrum stream has in addition to normal stream:
        #  * information about the current bandwidth displayed (avg. spectrum)
        #  * coordinates of 1st point (1-point, line)
        #  * coordinates of 2nd point (line)

        if len(image.shape) == 3:
            # force 5D
            image = image[:, numpy.newaxis, numpy.newaxis, :, :]
        elif len(image.shape) != 5 or image.shape[1:3] != (1, 1):
            logging.error("Cannot handle data of shape %s", image.shape)
            raise NotImplementedError("SpectrumStream needs a cube data")

        # ## this is for "average spectrum" projection
        try:
            # cached list of wavelength for each pixel pos
            self._wl_px_values = spectrum.get_wavelength_per_pixel(image)
        except (ValueError, KeyError):
            # useless polynomial => just show pixels values (ex: -50 -> +50 px)
            # TODO: try to make them always int?
            max_bw = image.shape[0] // 2
            min_bw = (max_bw - image.shape[0]) + 1
            self._wl_px_values = range(min_bw, max_bw + 1)
            assert (len(self._wl_px_values) == image.shape[0])
            unit_bw = "px"
            cwl = (max_bw + min_bw) // 2
            width = image.shape[0] // 12
        else:
            min_bw, max_bw = self._wl_px_values[0], self._wl_px_values[-1]
            unit_bw = "m"
            cwl = (max_bw + min_bw) / 2
            width = (max_bw - min_bw) / 12

        # TODO: allow to pass the calibration data as argument to avoid
        # recomputing the data just after init?
        # Spectrum efficiency compensation data: None or a DataArray (cf acq.calibration)
        self.efficiencyCompensation = model.VigilantAttribute(
            None, setter=self._setEffComp)

        # The background data (typically, an acquisition without ebeam).
        # It is subtracted from the acquisition data.
        # If set to None, a simple baseline background value is subtracted.
        self.background = model.VigilantAttribute(None,
                                                  setter=self._setBackground)

        # low/high values of the spectrum displayed
        self.spectrumBandwidth = model.TupleContinuous(
            (cwl - width, cwl + width),
            range=((min_bw, min_bw), (max_bw, max_bw)),
            unit=unit_bw,
            cls=(int, long, float))

        # Whether the (per bandwidth) display should be split intro 3 sub-bands
        # which are applied to RGB
        self.fitToRGB = model.BooleanVA(False)

        self._drange = None

        # This attribute is used to keep track of any selected pixel within the
        # data for the display of a spectrum
        self.selected_pixel = model.TupleVA((None, None))  # int, int

        # first point, second point in pixels. It must be 2 elements long.
        self.selected_line = model.ListVA([(None, None), (None, None)],
                                          setter=self._setLine)

        # The thickness of a point of a line (shared).
        # A point of width W leads to the average value between all the pixels
        # which are within W/2 from the center of the point.
        # A line of width W leads to a 1D spectrum taking into account all the
        # pixels which fit on an orthogonal line to the selected line at a
        # distance <= W/2.
        self.width = model.IntContinuous(1, [1, 50], unit="px")

        self.fitToRGB.subscribe(self.onFitToRGB)
        self.spectrumBandwidth.subscribe(self.onSpectrumBandwidth)
        self.efficiencyCompensation.subscribe(self._onCalib)
        self.background.subscribe(self._onCalib)

        self.raw = [image
                    ]  # for compatibility with other streams (like saving...)
        self._calibrated = image  # the raw data after calibration

        self._updateDRange()
        self._updateHistogram()
        self._updateImage()
Beispiel #30
0
    def __init__(self, name, role, device, channels, spectra, pwr_curve,
                 **kwargs):
        """
        device (string): name of the /dev comedi  device (ex: "/dev/comedi0")
        channels (list of (0<=int)): The output channel for each source, as
          numbered in the comedi subdevice.
        spectra (list of 5-tuple of float): the spectra for each output channel used.
         Each tuple represents 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.
        pwr_curve (list of dict (float -> 0<float)): Power curve segment map for
           each source. A segment map is a  series of voltage output on the
           analog output -> emission power of the light (W).
           It represents a series of linear segments to map the voltage output
           to the light emission. At least one pair should be provided.
           If no voltage is linked to 0W, then a 0V -> 0W mapping is used.
           The total curve should be monotonic.
        """
        # TODO: allow to give the unit of the power/pwr_curve ?

        model.Emitter.__init__(self, name, role, **kwargs)
        self._shape = ()

        try:
            self._device = comedi.open(device)
        #             self._fileno = comedi.fileno(self._device)
        except comedi.ComediError:
            raise ValueError("Failed to open DAQ device '%s'" % device)

        # Look for the analog output subdevice
        try:
            self._ao_subd = comedi.find_subdevice_by_type(
                self._device, comedi.SUBD_AO, 0)
            nchan = comedi.get_n_channels(self._device, self._ao_subd)
            if nchan < max(channels):
                raise ValueError(
                    "Device only has %d channels, while needed %d" %
                    (nchan, max(channels)))
        except comedi.ComediError:
            raise ValueError(
                "Failed to find an analogue output on DAQ device '%s'" %
                device)

        if len(channels) != len(spectra):
            raise ValueError(
                "spectra argument should have the same length as channels (%d)"
                % len(channels))
        if len(channels) != len(pwr_curve):
            raise ValueError(
                "pwr_curve argument should have the same length as channels (%d)"
                % len(channels))

        self._channels = channels

        # Check and store the power curves
        self._ranges = []
        self._pwr_curve = []
        for c, crv in zip(channels, pwr_curve):
            crv = [v for v in crv.items()]
            # Add 0W = 0V if nothing = 0W
            if 0 not in [w for v, w in crv]:
                crv.append((0, 0))
                logging.info(
                    "Adding 0V -> 0W mapping to pwr_curve for channel %d", c)
            # At least beginning and end values
            if len(crv) < 2:
                raise ValueError(
                    "pwr_curve for channel %d has less than 2 values: %s" %
                    (c, crv))
            # Check it's monotonic
            crv = sorted(crv, key=lambda v: v[0])
            if crv[0][1] < 0:
                raise ValueError(
                    "pwr_curve for channel %d has negative power: %g W" %
                    (c, crv[0][1]))
            if len(crv) != len(set(v for v, w in crv)):
                raise ValueError(
                    "pwr_curve for channel %d has identical voltages: %s" %
                    (c, crv))
            if not all(
                (crv[i][1] < crv[i + 1][1]) for i in range(len(crv) - 1)):
                raise ValueError(
                    "pwr_curve for channel %d is not monotonic: %s" % (c, crv))

            self._pwr_curve.append(crv)

            # Find the best range to use
            try:
                ri = comedi.find_range(self._device, self._ao_subd, c,
                                       comedi.UNIT_volt, crv[0][0], crv[-1][0])
            except comedi.ComediError:
                raise ValueError(
                    "Data range between %g and %g V is too high for hardware."
                    % (crv[0][0], crv[-1][0]))
            self._ranges.append(ri)

        # Check the spectra
        spect = []  # list of the 5 wavelength points
        for c, wls in zip(channels, spectra):
            if len(wls) != 5:
                raise ValueError(
                    "Spectra for channel %d doesn't have exactly 5 wavelength points: %s"
                    % (c, wls))
            if list(wls) != sorted(wls):
                raise ValueError(
                    "Spectra for channel %d has unsorted wavelengths: %s" %
                    (c, wls))
            for wl in wls:
                if not 0 < wl < 100e-6:
                    raise ValueError(
                        "Spectra for channel %d has unexpected wavelength = %f nm"
                        % (c, wl * 1e9))
            spect.append(tuple(wls))

        # Maximum power for channel to be used as a range for power
        max_power = tuple([crv[-1][1] for crv in self._pwr_curve])
        # Power value for each channel of the device
        self.power = model.ListContinuous(
            value=[0.] * len(self._channels),
            range=(
                tuple([0.] * len(self._channels)),
                max_power,
            ),
            unit="W",
            cls=(int, long, float),
        )
        self.power.subscribe(self._updatePower)

        # info on which channel is which wavelength
        self.spectra = model.ListVA(spect, unit="m", readonly=True)

        # make sure everything is off (turning on the HUB will turn on the lights)
        self.power.value = self.power.range[0]

        self._metadata = {model.MD_HW_NAME: self.getHwName()}
        lnx_ver = driver.get_linux_version()
        self._swVersion = "%s (driver %s, linux %s)" % (
            odemis.__version__, self.getSwVersion(), ".".join(
                "%s" % v for v in lnx_ver))
        self._metadata[model.MD_SW_VERSION] = self._swVersion
        self._metadata[model.MD_HW_VERSION] = self._hwVersion  # unknown