Exemple #1
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()
Exemple #2
0
    def __init__(self, microscope, main_app):
        super(CLAcqPlugin, self).__init__(microscope, main_app)
        # Can only be used with a microscope
        if not microscope:
            return
        else:
            # Check which stream the microscope supports
            main_data = self.main_app.main_data
            if not (main_data.ccd and main_data.ebeam):
                return

        self.exposureTime = main_data.ccd.exposureTime
        self.binning = main_data.ccd.binning
        # Trick to pass the component (ccd to binning_1d_from_2d())
        self.vaconf["binning"]["choices"] = (
            lambda cp, va, cf: gui.conf.util.binning_1d_from_2d(
                main_data.ccd, va, cf))
        self.xres = model.IntContinuous(10, (1, 1000), unit="px")
        self.yres = model.IntContinuous(10, (1, 1000), unit="px")
        self.stepsize = model.FloatVA(1e-6, unit="m")  # Just to show
        # Maximum margin is half the CCD FoV
        ccd_rect = get_ccd_fov(main_data.ccd)
        max_margin = max(ccd_rect[2] - ccd_rect[0],
                         ccd_rect[3] - ccd_rect[1]) / 2
        self.roi_margin = model.FloatContinuous(0, (0, max_margin), unit="m")
        self.filename = model.StringVA("a.tiff")

        self.xres.subscribe(self._update_stepsize)
        self.yres.subscribe(self._update_stepsize)

        self.addMenu("Acquisition/CL acquisition...", self.start)
Exemple #3
0
    def __init__(self, fake_img):
        """
        Use .fake_img to change the image sent by the ccd
        Args:
            fake_img: 2D DataArray
        """
        super(FakeCCD, self).__init__("testccd", "ccd")
        self.exposureTime = model.FloatContinuous(0.1, (1e-6, 1000), unit="s")
        res = fake_img.shape[1], fake_img.shape[0]  # X, Y
        depth = 2 ** (fake_img.dtype.itemsize * 8)
        self.shape = (res[0], res[1], depth)
        self.binning = model.TupleContinuous((1, 1), [(1, 1), (8, 8)],
                                       cls=(int, long, float), unit="")
        self.resolution = model.ResolutionVA(res, [(1, 1), res])
        self.readoutRate = model.FloatVA(1e9, unit="Hz", readonly=True)

        pxs_sens = fake_img.metadata.get(model.MD_SENSOR_PIXEL_SIZE, (10e-6, 10e-6))
        self.pixelSize = model.VigilantAttribute(pxs_sens, unit="m", readonly=True)

        self.data = CCDDataFlow(self)
        self._acquisition_thread = None
        self._acquisition_lock = threading.Lock()
        self._acquisition_init_lock = threading.Lock()
        self._acquisition_must_stop = threading.Event()
        self.fake_img = fake_img

        self._metadata = fake_img.metadata
Exemple #4
0
 def __init__(self, name):
     model.Emitter.__init__(self, name, "fakeebeam", parent=None)
     self._shape = (2048, 2048)
     self.resolution = model.ResolutionVA((256, 256), [(1, 1), self._shape])
     self.pixelSize = model.VigilantAttribute((1e-9, 1e-9),
                                              unit="m",
                                              readonly=True)
     self.magnification = model.FloatVA(1000.)
Exemple #5
0
    def test_readonly(self):
        prop = model.FloatVA(2.0, readonly=True)
        try:
            prop.value = 6.0
            self.fail("Modifying a readonly property should not be allowed.")
        except model.NotSettableError:
            pass  # as it should be

        self.assertTrue(prop.value == 2)
Exemple #6
0
 def test_setter(self):
     """
     check the delegation
     """
     prop = model.FloatVA(2.0, setter=self.delegate_set_float)
     # maybe it should be 3.0? But it's better not to call the delegate setter
     # anyway, the owner can always also call the setter by itself
     self.assertEqual(prop.value, 2.0)
     prop.value = 10.0
     self.assertEqual(prop.value, 11.0)
Exemple #7
0
    def __init__(self, name):
        self.name = model.StringVA(name)

        # a thumbnail version of what is displayed
        self.thumbnail = VigilantAttribute(None)  # contains a wx.Image

        # Last time the image of the view was changed. It's actually mostly
        # a trick to allow other parts of the GUI to know when the (theoretical)
        # composited image has changed.
        self.lastUpdate = model.FloatVA(time.time(), unit="s")
Exemple #8
0
 def __init__(self, name):
     model.Emitter.__init__(self, name, "fakeebeam", parent=None)
     self._shape = (2048, 2048)
     self.dwellTime = model.FloatContinuous(1e-6, (1e-6, 1), unit="s")
     self.resolution = model.ResolutionVA((256, 256), [(1, 1), self._shape])
     self.pixelSize = model.VigilantAttribute((1e-9, 1e-9),
                                              unit="m",
                                              readonly=True)
     self.magnification = model.FloatVA(1000.)
     self.scale = model.TupleContinuous((1, 1), [(1, 1), self._shape],
                                        cls=(int, long, float),
                                        unit="")
     self.translation = model.TupleContinuous((0, 0), ((0, 0), (0, 0)),
                                              cls=(int, long, float),
                                              unit="px")
Exemple #9
0
    def test_notify_init(self):
        prop = model.FloatVA(2.0)
        self.called = 0
        # now count
        prop.subscribe(self.callback_test_notify, init=True)  # +1
        prop.value = 3.0  # +1
        prop.value = 0  # +1
        prop.value = 0.0  # nothing because same value
        try:
            prop.value = "coucou"
            self.fail("Assigning string to a float should not be allowed.")
        except TypeError:
            pass  # as it should be
        prop.unsubscribe(self.callback_test_notify)

        prop.value = 12  # no more counting

        self.assertTrue(prop.value == 12)
        self.assertTrue(self.called == 3)
Exemple #10
0
    def test_getter_setter(self):
        """
        check the delegation to getter and setter
        """
        propt = model.FloatVA(time.time(), getter=self.delegate_get_float,
                              setter=self.delegate_set_float, unit="s")

        self.called = 0
        # now count
        propt.subscribe(self.callback_test_notify)

        t1 = propt.value  # time now
        propt.value = 10.0  # will "set" 11 != time, => +1 change
        t2 = propt.value  # new time
        self.assertGreater(t2, t1)

        self.assertEqual(self.called, 1)

        propt.unsubscribe(self.callback_test_notify)
Exemple #11
0
    def test_weakref(self):
        """
        checks that even if an object has a method subscribed to a property,
          it will be garbage-collected when not used anymore and its
          subscription dropped.
        """
        prop = model.FloatVA(2.0)
        o = LittleObject()
        wo = weakref.ref(o)
        assert (wo() is not None)

        prop.subscribe(o.callback)
        prop.value = 6.0  # +1
        assert (o.called == 1)

        del o
        assert (wo() is None)

        prop.value = 1
        assert (prop.value == 1)
Exemple #12
0
    def __init__(self, microscope, main_app):
        super(CLAcqPlugin, self).__init__(microscope, main_app)
        # Can only be used with a microscope
        if not microscope:
            return
        else:
            # Check which stream the microscope supports
            main_data = self.main_app.main_data
            if not (main_data.ccd and main_data.ebeam):
                return

        self.exposureTime = main_data.ccd.exposureTime
        self.binning = main_data.ccd.binning
        self.xres = model.IntContinuous(10, (1, 1000), unit="px")
        self.yres = model.IntContinuous(10, (1, 1000), unit="px")
        self.stepsize = model.FloatVA(1e-6, unit="m")  # Just to show
        self.filename = model.StringVA("a.tiff")

        self.xres.subscribe(self._update_stepsize)
        self.yres.subscribe(self._update_stepsize)

        self.addMenu("Acquisition/CL acquisition...", self.start)
Exemple #13
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()
Exemple #14
0
    def __init__(self,
                 name,
                 role,
                 mag,
                 mag_choices=None,
                 na=0.95,
                 ri=1,
                 pole_pos=None,
                 x_max=None,
                 hole_diam=None,
                 focus_dist=None,
                 parabola_f=None,
                 rotation=None,
                 **kwargs):
        """
        name (string): should be the name of the product (for metadata)
        mag (float > 0): magnification ratio
        mag_choices (None, list of floats > 0): list of allowed magnification ratio.
          If None, the magnification will be allowed for any value between 1e-3 to 1e6.
        na (float > 0): numerical aperture
        ri (0.01 < float < 100): refractive index
        pole_pos (2 floats > 0): position of the pole on the CCD (in px, without
          binning, with the top-left pixel as origin).
          Used for angular resolved imaging on SPARC (only). cf MD_AR_POLE
        x_max (float): the distance between the parabola origin and the cutoff
          position (in meters). Used for angular resolved imaging on SPARC (only).
          cf MD_AR_XMAX
        hole_diam (float): diameter of the hole in the mirror (in meters). Used
          for angular resolved imaging on SPARC (only).
          cf MD_AR_HOLE_DIAMETER
        focus_dist (float): the vertical mirror cutoff, iow the min distance
          between the mirror and the sample (in meters). Used for angular
          resolved imaging on SPARC (only).
          cf MD_AR_FOCUS_DISTANCE
        parabola_f (float): parabola_parameter=1/4f. Used for angular
          resolved imaging on SPARC (only).
          cf MD_AR_PARABOLA_F
        rotation (0<float<2*pi): rotation between the Y axis of the SEM
          referential and the optical path axis. Used on the SPARC to report
          the rotation between the AR image and the SEM image.
        """
        assert (mag > 0)
        model.HwComponent.__init__(self, name, role, **kwargs)

        self._swVersion = "N/A (Odemis %s)" % odemis.__version__
        self._hwVersion = name

        # allow the user to modify the value, if the lens is manually changed
        if mag_choices is None:
            self.magnification = model.FloatContinuous(mag,
                                                       range=(1e-3, 1e6),
                                                       unit="")
        else:
            mag_choices = frozenset(mag_choices)
            if mag not in mag_choices:
                raise ValueError("mag (%s) is not within the mag_choices %s" %
                                 (mag, mag_choices))
            self.magnification = model.FloatEnumerated(mag,
                                                       choices=mag_choices,
                                                       unit="")

        self.numericalAperture = model.FloatContinuous(na,
                                                       range=(1e-6, 1e3),
                                                       unit="")
        self.refractiveIndex = model.FloatContinuous(ri,
                                                     range=(0.01, 10),
                                                     unit="")

        if pole_pos is not None:
            if (not isinstance(pole_pos, collections.Iterable)
                    or len(pole_pos) != 2
                    or any(not 0 < v < 1e6 for v in pole_pos)):
                raise ValueError("pole_pos must be 2 positive values, got %s" %
                                 pole_pos)
            self.polePosition = model.ResolutionVA(tuple(pole_pos),
                                                   rng=((0, 0), (1e6, 1e6)),
                                                   unit="px")
        if x_max is not None:
            self.xMax = model.FloatVA(x_max, unit="m")
        if hole_diam is not None:
            self.holeDiameter = model.FloatVA(hole_diam, unit="m")
        if focus_dist is not None:
            self.focusDistance = model.FloatVA(focus_dist, unit="m")
        if parabola_f is not None:
            self.parabolaF = model.FloatVA(parabola_f, unit="m")
        if rotation is not None:
            self.rotation = model.FloatContinuous(rotation, (0, 2 * math.pi),
                                                  unit="rad")
Exemple #15
0
    def __init__(self,
                 name,
                 role,
                 mag,
                 mag_choices=None,
                 na=0.95,
                 ri=1,
                 pole_pos=None,
                 x_max=None,
                 hole_diam=None,
                 focus_dist=None,
                 parabola_f=None,
                 rotation=None,
                 configurations=None,
                 **kwargs):
        """
        name (string): should be the name of the product (for metadata)
        mag (float > 0): magnification ratio
        mag_choices (None, list of floats > 0): list of allowed magnification ratio.
          If None, the magnification will be allowed for any value between 1e-3 to 1e6.
        na (float > 0): numerical aperture
        ri (0.01 < float < 100): refractive index
        pole_pos (2 floats > 0): position of the pole on the CCD (in px, without
          binning, with the top-left pixel as origin).
          Used for angular resolved imaging on SPARC (only). cf MD_AR_POLE
        x_max (float): the distance between the parabola origin and the cutoff
          position (in meters). Used for angular resolved imaging on SPARC (only).
          cf MD_AR_XMAX
        hole_diam (float): diameter of the hole in the mirror (in meters). Used
          for angular resolved imaging on SPARC (only).
          cf MD_AR_HOLE_DIAMETER
        focus_dist (float): the vertical mirror cutoff, iow the min distance
          between the mirror and the sample (in meters). Used for angular
          resolved imaging on SPARC (only).
          cf MD_AR_FOCUS_DISTANCE
        parabola_f (float): parabola_parameter=1/4f. Used for angular
          resolved imaging on SPARC (only).
          cf MD_AR_PARABOLA_F
        rotation (0<float<2*pi): rotation between the Y axis of the SEM
          referential and the optical path axis. Used on the SPARC to report
          the rotation between the AR image and the SEM image.
        configurations (dict str -> (dict str -> value)): {configuration name -> {attribute name -> value}}
          All the configurations supported and their settings. A "configuration" is a set of attributes with
          predefined values. When this argument is specified, a .configuration attribute will be available,
          with each configuration name, and changing it will automatically set all the associated attributes
          to their predefined value.
        """
        assert (mag > 0)
        model.HwComponent.__init__(self, name, role, **kwargs)

        self._swVersion = "N/A (Odemis %s)" % odemis.__version__
        self._hwVersion = name

        # allow the user to modify the value, if the lens is manually changed
        if mag_choices is None:
            self.magnification = model.FloatContinuous(mag,
                                                       range=(1e-3, 1e6),
                                                       unit="")
        else:
            mag_choices = frozenset(mag_choices)
            if mag not in mag_choices:
                raise ValueError("mag (%s) is not within the mag_choices %s" %
                                 (mag, mag_choices))
            self.magnification = model.FloatEnumerated(mag,
                                                       choices=mag_choices,
                                                       unit="")

        self.numericalAperture = model.FloatContinuous(na,
                                                       range=(1e-6, 1e3),
                                                       unit="")
        self.refractiveIndex = model.FloatContinuous(ri,
                                                     range=(0.01, 10),
                                                     unit="")

        if pole_pos is not None:
            if (not isinstance(pole_pos, collections.Iterable)
                    or len(pole_pos) != 2
                    or any(not 0 < v < 1e6 for v in pole_pos)):
                raise ValueError("pole_pos must be 2 positive values, got %s" %
                                 pole_pos)
            self.polePosition = model.ResolutionVA(tuple(pole_pos),
                                                   rng=((0, 0), (1e6, 1e6)),
                                                   unit="px")
        if x_max is not None:
            self.xMax = model.FloatVA(x_max, unit="m")
        if hole_diam is not None:
            self.holeDiameter = model.FloatVA(hole_diam, unit="m")
        if focus_dist is not None:
            self.focusDistance = model.FloatVA(focus_dist, unit="m")
        if parabola_f is not None:
            self.parabolaF = model.FloatVA(parabola_f, unit="m")
        if rotation is not None:
            self.rotation = model.FloatContinuous(rotation, (0, 2 * math.pi),
                                                  unit="rad")
        if configurations is not None:
            self._configurations = configurations

            # Find the configuration which is closest to the current settings
            def _compare_config(cn):
                settings = configurations[cn]
                score = 0
                for arg, value in settings.items():
                    try:
                        vaname = CONFIG_2_VA[arg]
                        va = getattr(self, vaname)
                    except (KeyError, AttributeError):
                        raise ValueError(
                            "Attribute name predefined in the configuration required"
                        )
                    if value == va.value:
                        score += 1
                return score

            current_conf = max(configurations, key=_compare_config)
            self.configuration = model.StringEnumerated(
                current_conf,
                choices=set(configurations.keys()),
                setter=self._setConfiguration)
Exemple #16
0
    def __init__(self,
                 name,
                 role,
                 image,
                 dependencies=None,
                 daemon=None,
                 blur_factor=1e4,
                 max_res=None,
                 **kwargs):
        """
        dependencies (dict string->Component): If "focus" is passed, and it's an
            actuator with a z axis, the image will be blurred based on the
            position, to simulate a focus axis.
        image (str or None): path to a file to use as fake image (relative to the directory of this class)
        max_res (tuple of (int, int) or None): maximum resolution to clip simulated image, if None whole image shape
            will be used. The simulated image will be a part of the original image based on the MD_POS metadata.
        """
        # TODO: support transpose? If not, warn that it's not accepted
        # fake image setup
        image = str(image)
        # ensure relative path is from this file
        if not os.path.isabs(image):
            image = os.path.join(os.path.dirname(__file__), image)
        converter = dataio.find_fittest_converter(image, mode=os.O_RDONLY)
        self._img = converter.read_data(image)[0]  # can be RGB or greyscale

        model.DigitalCamera.__init__(self,
                                     name,
                                     role,
                                     dependencies=dependencies,
                                     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

        def clip_max_res(img_res):
            if len(max_res) != 2:
                raise ValueError("Shape of max_res should be = 2.")
            return tuple(min(x, y) for x, y in zip(
                img_res, max_res))  # in case max_res > image 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._img_res = res  # Original image shape in case it's clipped
            if max_res:
                res = clip_max_res(res)
            self._shape = res + imshp[-1::]  # X, Y, C
            # indicate it's RGB pixel-per-pixel ordered
            self._img.metadata[model.MD_DIMS] = "YXC"
        else:
            self._img_res = imshp[::
                                  -1]  # Original image shape in case it's clipped
            res = imshp[::-1] if max_res is None else tuple(max_res)
            if max_res:
                res = clip_max_res(res)
            self._shape = res  # X, Y,...
        # TODO: handle non integer dtypes
        depth = 2**(self._img.dtype.itemsize * 8)
        self._shape += (depth, )

        self._resolution = res
        self.resolution = model.ResolutionVA(self._resolution,
                                             ((1, 1), self._resolution),
                                             setter=self._setResolution)

        self._binning = (1, 1)
        self.binning = model.ResolutionVA(self._binning, ((1, 1), (16, 16)),
                                          setter=self._setBinning)

        hlf_shape = (self._shape[0] // 2 - 1, self._shape[1] // 2 - 1)
        tran_rng = [(-hlf_shape[0], -hlf_shape[1]),
                    (hlf_shape[0], hlf_shape[1])]
        self._translation = (0, 0)
        self.translation = model.ResolutionVA(self._translation,
                                              tran_rng,
                                              unit="px",
                                              cls=(int, long),
                                              setter=self._setTranslation)

        self._orig_exp = self._img.metadata.get(model.MD_EXP_TIME, 0.1)  # s
        self.exposureTime = model.FloatContinuous(self._orig_exp, (1e-3, 10),
                                                  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,
            model.MD_DET_TYPE: model.MD_DT_INTEGRATING
        }

        # Set the amount of blurring during defocusing.
        self._blur_factor = float(blur_factor)

        try:
            focuser = dependencies["focus"]
            if (not isinstance(focuser, model.ComponentBase)
                    or not hasattr(focuser, "axes")
                    or not isinstance(focuser.axes, dict)
                    or "z" not in focuser.axes):
                raise ValueError(
                    "focus %s must be a Actuator with a 'z' axis" %
                    (focuser, ))
            self._focus = focuser

            # The "good" focus is at the current position
            self._good_focus = self._focus.position.value["z"]
            self._metadata[model.MD_FAV_POS_ACTIVE] = {"z": self._good_focus}
            logging.debug("Simulating focus, with good focus at %g m",
                          self._good_focus)
        except (TypeError, KeyError):
            logging.info("Will not simulate focus")
            self._focus = None

        # Simple implementation of the flow: we keep generating images and if
        # there are subscribers, they'll receive it.
        self.data = SimpleDataFlow(self)
        self._generator = None
        # Convenience event for the user to connect and fire
        self.softwareTrigger = model.Event()

        # Include a thread which creates or fixes an hardware error in the simcam on the basis of the presence of the
        # file ERROR_STATE_FILE in model.BASE_DIRECTORY
        self._is_running = True
        self._error_creation_thread = threading.Thread(
            target=self._state_error_run, name="Creating and state error")
        self._error_creation_thread.daemon = True
        self._error_creation_thread.start()
Exemple #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()
Exemple #18
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)
        # ensure relative path is from this file
        if not os.path.isabs(image):
            image = os.path.join(os.path.dirname(__file__), image)
        converter = dataio.find_fittest_converter(image, mode=os.O_RDONLY)
        self._img = converter.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,)

        self._resolution = res
        self.resolution = model.ResolutionVA(self._resolution,
                              ((1, 1),
                               self._resolution), setter=self._setResolution)

        self._binning = (1, 1)
        self.binning = model.ResolutionVA(self._binning,
                              ((1, 1), (16, 16)), setter=self._setBinning)

        hlf_shape = (self._shape[0] // 2 - 1, self._shape[1] // 2 - 1)
        tran_rng = [(-hlf_shape[0], -hlf_shape[1]),
                    (hlf_shape[0], hlf_shape[1])]
        self._translation = (0, 0)
        self.translation = model.ResolutionVA(self._translation, tran_rng,
                                              cls=(int, long), unit="px",
                                              setter=self._setTranslation)

        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,
                          model.MD_DET_TYPE: model.MD_DT_INTEGRATING}

        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 = SimpleDataFlow(self)
        self._generator = None
        # Convenience event for the user to connect and fire
        self.softwareTrigger = model.Event()
Exemple #19
0
    def __init__(self, name, role, channel, node_idx, **kwargs):
        """

        channel (str): channel name of can bus
        node_idx (int): node index of focus tracker
        """
        model.HwComponent.__init__(self, name, role, **kwargs)
        # Connect to the CANbus and the CANopen network.
        self.network = canopen.Network()
        bustype = 'socketcan' if channel != 'fake' else 'virtual'
        try:
            self.network.connect(bustype=bustype, channel=channel)
        except IOError as exp:
            if exp.errno == 19:
                raise HwError("Focus Tracker is not connected.")
            else:
                raise
        self.network.check()
        object_dict = pkg_resources.resource_filename("odemis.driver",
                                                      "FocusTracker.eds")
        if channel == 'fake':
            self.node = FakeRemoteNode(node_idx, object_dict)
        else:
            self.node = canopen.RemoteNode(node_idx, object_dict)
        self.network.add_node(self.node)
        self._swVersion = "python-canopen v%s , python-can v%s" % (
            canopen.__version__, can.__version__)
        rev_num = self.node.sdo["Identity Object"]["Revision number"].raw
        minor = rev_num & 0xffff
        major = (rev_num >> 16) & 0xffff
        self._hwVersion = "{}.{}".format(major, minor)
        # Create SDO communication objects to communicate
        self._position_sdo = self.node.sdo["AI Input PV"][1]
        self._target_pos_sdo = self.node.sdo["CO Set Point W"][1]

        # Read PID gains from the device (and set the current metadata)
        self._proportional_gain_sdo = self.node.sdo[
            'CO Proportional Band Xp1'][1]
        self._integral_gain_sdo = self.node.sdo['CO Integral Action Time Tn1'][
            1]
        self._derivative_gain_sdo = self.node.sdo[
            'CO Derivative Action Time Tv1'][1]

        self._tracking_sdo = self.node.sdo['Controller On/ Off'][1]
        # set VAs only if there is hardware, channel = '' if no hardware
        self._metadata[model.MD_GAIN_P] = self._proportional_gain_sdo.raw
        self._metadata[model.MD_GAIN_I] = self._integral_gain_sdo.raw
        self._metadata[model.MD_GAIN_I] = self._derivative_gain_sdo.raw
        self.targetPosition = model.FloatContinuous(
            self._target_pos_sdo.raw,
            TARGET_POSITION_RANGE,
            unit="m",
            getter=self._get_target_pos,
            setter=self._set_target_pos)
        self.position = model.FloatVA(self._position_sdo.raw,
                                      unit="m",
                                      readonly=True,
                                      getter=self._get_position)
        self.tracking = model.BooleanVA(self._tracking_sdo.raw,
                                        getter=self._get_tracking,
                                        setter=self._set_tracking)
Exemple #20
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()
Exemple #21
0
    def __init__(self,
                 name,
                 role,
                 image,
                 dependencies=None,
                 daemon=None,
                 blur_factor=1e4,
                 resolution=None,
                 **kwargs):
        '''
        dependencies (dict string->Component): If "focus" is passed, and it's an
            actuator with a z axis, the image will be blurred based on the
            position, to simulate a focus axis.
        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)
        # ensure relative path is from this file
        if not os.path.isabs(image):
            image = os.path.join(os.path.dirname(__file__), image)
        converter = dataio.find_fittest_converter(image, mode=os.O_RDONLY)
        self._imgs = converter.read_data(image)  # can be RGB or greyscale
        model.DigitalCamera.__init__(self,
                                     name,
                                     role,
                                     dependencies=dependencies,
                                     daemon=daemon,
                                     **kwargs)

        for i, img in enumerate(self._imgs):
            if img.ndim > 3:  # remove dims of length 1
                self._imgs[i] = numpy.squeeze(img)
            imshp = img.shape
            if len(imshp) == 3 and imshp[i] in {3, 4}:
                # CYX, change it to YXC, to simulate a RGB detector
                self._imgs[i] = util.img.ensureYXC(img)

        for img in self._imgs[1:]:
            if self._imgs[0].shape != self._imgs[i].shape:
                raise ValueError("all images must have the same resolution")
        imshp = self._imgs[0].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
            if resolution:
                if resolution >= imshp[-2::-1]:
                    res = tuple(resolution)
            else:
                res = imshp[-2::-1]
            self._shape = res + imshp[-1::]  # X, Y, C
        else:
            if resolution:
                res = tuple(resolution)
            else:
                res = imshp[::-1]

            self._shape = res  # X, Y,...
        # TODO: handle non integer dtypes
        depth = 2**(self._imgs[0].dtype.itemsize * 8)
        self._shape += (depth, )

        self._resolution = res
        self.resolution = model.ResolutionVA(self._resolution,
                                             ((1, 1), self._resolution),
                                             setter=self._setResolution)

        self._binning = (1, 1)
        self.binning = model.ResolutionVA(self._binning, ((1, 1), (16, 16)),
                                          setter=self._setBinning)

        hlf_shape = (self._shape[0] // 2 - 1, self._shape[1] // 2 - 1)
        tran_rng = [(-hlf_shape[0], -hlf_shape[1]),
                    (hlf_shape[0], hlf_shape[1])]
        self._translation = (0, 0)
        self.translation = model.ResolutionVA(self._translation,
                                              tran_rng,
                                              cls=(int, long),
                                              unit="px",
                                              setter=self._setTranslation)

        exp = self._imgs[0].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._imgs[0].metadata.get(model.MD_PIXEL_SIZE, (10e-6, 10e-6))
        mag = self._imgs[0].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,
            model.MD_DET_TYPE: model.MD_DT_INTEGRATING,
            model.MD_PIXEL_SIZE: pxs
        }

        # Set the amount of blurring during defocusing.
        self._blur_factor = float(blur_factor)

        try:
            focuser = dependencies["focus"]
            if (not isinstance(focuser, model.ComponentBase)
                    or not hasattr(focuser, "axes")
                    or not isinstance(focuser.axes, dict)
                    or "z" not in focuser.axes):
                raise ValueError(
                    "focus %s must be a Actuator with a 'z' axis" %
                    (focuser, ))
            self._focus = focuser

            # The "good" focus is at the current position
            self._good_focus = self._focus.position.value["z"]
            self._metadata[model.MD_FAV_POS_ACTIVE] = {"z": self._good_focus}
            logging.debug("Simulating focus, with good focus at %g m",
                          self._good_focus)
        except (TypeError, KeyError):
            logging.info("Will not simulate focus")
            self._focus = None

        try:
            stage = dependencies["stage"]
            if (not isinstance(stage, model.ComponentBase)
                    or not hasattr(stage, "axes")
                    or not isinstance(stage.axes, dict)):
                raise ValueError("stage %s must be a Actuator with a 'z' axis",
                                 stage)
            self._stage = stage
            if resolution == None:
                raise ValueError("resolution is %s", resolution)
            # the position of the center of the image
            self._orig_stage_pos = self._stage.position.value[
                "x"], self._stage.position.value["y"]
            logging.debug("Simulating stage at %s m", self._orig_stage_pos)
        except (TypeError, KeyError):
            logging.info("Will not simulate stage")
            self._stage = None

        # Simple implementation of the flow: we keep generating images and if
        # there are subscribers, they'll receive it.
        self.data = SimpleDataFlow(self)
        self._generator = None
        # Convenience event for the user to connect and fire
        self.softwareTrigger = model.Event()