Esempio n. 1
0
    def __init__(self, microscope, main_app):
        super(RGBCLIntensity, self).__init__(microscope, main_app)

        # Can only be used on a SPARC with a CL-intensity detector
        if not microscope:
            return
        try:
            self.ebeam = model.getComponent(role="e-beam")
            self.cldetector = model.getComponent(role="cl-detector")
            self.filterwheel = model.getComponent(role="cl-filter")
            self.sed = model.getComponent(role="se-detector")
            # We could also check the filter wheel has at least 3 filters, but
            # let's not be too picky, if the user has installed the plugin, he
            # probably wants to use it anyway.
        except LookupError:
            logging.info("Hardware not found, cannot use the RGB CL plugin")
            return

        # The SEM survey and CLi stream (will be updated when showing the window)
        self._survey_s = None
        self._cl_int_s = None
        self._acqui_tab = main_app.main_data.getTabByName(
            "sparc_acqui").tab_data_model

        # The settings to be displayed in the dialog
        # TODO: pick better default filters than first 3 filters
        # => based on the wavelengths fitting best RGB, or the names (eg, "Blue"),
        # and avoid "pass-through".
        fbchoices = self.filterwheel.axes["band"].choices
        if isinstance(fbchoices, dict):
            fbvalues = sorted(fbchoices.keys())
        else:
            fbvalues = fbchoices
        # FloatEnumerated because filter positions can be in rad (ie, not int positions)
        self.filter1 = model.FloatEnumerated(fbvalues[0], choices=fbchoices)
        self.filter2 = model.FloatEnumerated(fbvalues[min(
            1,
            len(fbvalues) - 1)],
                                             choices=fbchoices)
        self.filter3 = model.FloatEnumerated(fbvalues[min(
            2,
            len(fbvalues) - 1)],
                                             choices=fbchoices)

        self._filters = [self.filter1, self.filter2, self.filter3]
        self._colours = [(0, 0, 255), (0, 255, 0), (255, 0, 0)]  # B, G, R

        self.filename = model.StringVA("a.tiff")
        self.expectedDuration = model.VigilantAttribute(1,
                                                        unit="s",
                                                        readonly=True)

        self.addMenu("Acquisition/RGB CL intensity...", self.start)
Esempio n. 2
0
    def __init__(self, name, detector, sed, emitter, opm=None):
        """
        name (string): user-friendly name of this stream
        detector (Detector): the monochromator
        sed (Detector): the se-detector
        emitter (Emitter): the emitter (eg: ebeam scanner)
        spectrograph (Actuator): the spectrograph
        """
        self.name = model.StringVA(name)

        # Hardware Components, detector is the correlator, sed is the secondary electron image and the emitter is the electron beam
        self._detector = detector
        self._sed = sed
        self._emitter = emitter
        self._opm = opm

        self.is_active = model.BooleanVA(False)

        #dwell time and exposure time are the same thing in this case
        self.dwellTime = model.FloatContinuous(1, range=self._emitter.dwellTime.range,
                                               unit="s")
        # pixelDuration of correlator, this can be shortened once implemented as choices.
        self.pixelDuration = model.FloatEnumerated(512e-12,
                                choices={4e-12, 8e-12, 16e-12, 32e-12, 64e-12, 128e-12, 256e-12, 512e-12},
                                unit="s",
                                )
        #Sync Offset time correlator
        self.syncOffset = self._detector.syncOffset
        #Sync Divider time correlator
        self.syncDiv = self._detector.syncDiv 
        
        # Distance between the center of each pixel
        self.stepsize = model.FloatContinuous(1e-6, (1e-9, 1e-4), unit="m")

        # Region of acquisition. ROI form is LEFT Top RIGHT Bottom, relative to full field size
        self.roi = model.TupleContinuous((0, 0, 1, 1),
                                         range=((0, 0, 0, 0), (1, 1, 1, 1)),
                                         cls=(int, long, float))

        # Cropvalue that  can be used to crop the data for better visualization in odemis
        self.cropvalue = model.IntContinuous(1024, (1, 65536), unit="px")

        # For drift correction
        self.dcRegion = model.TupleContinuous(UNDEFINED_ROI,
                                              range=((0, 0, 0, 0), (1, 1, 1, 1)),
                                              cls=(int, long, float))
        self.dcDwellTime = model.FloatContinuous(emitter.dwellTime.range[0],
                                                 range=emitter.dwellTime.range, unit="s")
        #number of drift corrections per scanning pixel
        self.nDC = model.IntContinuous(1, (1, 20))

        # For acquisition
        self.tc_data = None
        self.tc_data_received = threading.Event()
        self.sem_data = []
        self.sem_data_received = threading.Event()
        self._hw_settings = None
Esempio n. 3
0
    def __init__(self, name, role, parent, daemon=None, **kwargs):
        super(StreakUnit, self).__init__(name,
                                         role,
                                         parent=parent,
                                         daemon=daemon,
                                         **kwargs)  # init HwComponent

        self.parent = parent

        self._metadata[model.MD_HW_VERSION] = "Simulated streak unit C10627"

        # VAs
        self.streakMode = model.BooleanVA(
            False,
            setter=self._setStreakMode)  # default False see set params above

        gain = 0
        range_gain = (0, 63)
        self.MCPGain = model.IntContinuous(gain,
                                           range_gain,
                                           setter=self._setMCPGain)

        timeRange = 0.000000001
        choices = {
            0.000000001, 0.000000002, 0.000000005, 0.00000001, 0.00000002,
            0.00000005, 0.0000001, 0.0000002, 0.0000005, 0.000001, 0.000002,
            0.000005, 0.00001, 0.00002, 0.00005, 0.0001, 0.0002, 0.0005, 0.001,
            0.002, 0.005, 0.01
        }
        self.timeRange = model.FloatEnumerated(timeRange,
                                               choices,
                                               setter=self._setTimeRange,
                                               unit="s")

        self._metadata[model.MD_STREAK_TIMERANGE] = self.timeRange.value
        self._metadata[model.MD_STREAK_MCPGAIN] = self.MCPGain.value
        self._metadata[model.MD_STREAK_MODE] = self.streakMode.value
Esempio n. 4
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")
Esempio n. 5
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)
Esempio n. 6
0
    def __init__(self, name, role, parent, **kwargs):
        # It will set up ._shape and .parent
        model.Emitter.__init__(self, name, role, parent=parent, **kwargs)

        fake_img = self.parent.fake_img
        if parent._drift_period:
            # half the size, to keep some margin for the drift
            self._shape = tuple(v // 2 for v in fake_img.shape[::-1])
        else:
            self._shape = fake_img.shape[::-1]

        # next two values are just to determine the pixel size
        # Distance between borders if magnification = 1. It should be found out
        # via calibration. We assume that image is square, i.e., VFV = HFV
        self._hfw_nomag = 0.25  # m

        # pixelSize is the same as MD_PIXEL_SIZE, with scale == 1
        # == smallest size/ between two different ebeam positions
        pxs = fake_img.metadata[model.MD_PIXEL_SIZE]
        self.pixelSize = model.VigilantAttribute(pxs, unit="m", readonly=True)

        # the horizontalFoV VA indicates that it's possible to control the zoom
        hfv = pxs[0] * self._shape[0]
        self.horizontalFoV = model.FloatContinuous(hfv, range=[1e-6, 10e-3],
                                                   unit="m")
        self.magnification = model.VigilantAttribute(self._hfw_nomag / hfv,
                                                     unit="", readonly=True)

        # (.resolution), .translation, .rotation, and .scaling are used to
        # define the conversion from coordinates to a region of interest.

        # (float, float) in px => moves center of acquisition by this amount
        # independent of scale and rotation.
        tran_rng = [(-self._shape[0] / 2, -self._shape[1] / 2),
                    (self._shape[0] / 2, self._shape[1] / 2)]
        self.translation = model.TupleContinuous((0, 0), tran_rng,
                                              cls=(int, long, float), unit="px",
                                              setter=self._setTranslation)

        # .resolution is the number of pixels actually scanned. If it's less than
        # the whole possible area, it's centered.
        resolution = (self._shape[0] // 8, self._shape[1] // 8)
        self.resolution = model.ResolutionVA(resolution, [(1, 1), self._shape],
                                             setter=self._setResolution)
        self._resolution = resolution

        # (float, float) as a ratio => how big is a pixel, compared to pixelSize
        # it basically works the same as binning, but can be float
        # (Default to scan the whole area)
        self._scale = (self._shape[0] / resolution[0], self._shape[1] / resolution[1])
        self.scale = model.TupleContinuous(self._scale, [(1, 1), self._shape],
                                           cls=(int, long, float),
                                           unit="", setter=self._setScale)
        self.scale.subscribe(self._onScale, init=True) # to update metadata

        # (float) in rad => rotation of the image compared to the original axes
        # TODO: for now it's readonly because no rotation is supported
        self.rotation = model.FloatContinuous(0, [0, 2 * math.pi], unit="rad",
                                              readonly=True)

        self.dwellTime = model.FloatContinuous(1e-06, (1e-06, 1000), unit="s")

        # VAs to control the ebeam, purely fake
        self.power = model.FloatEnumerated(1, {0, 1})
        self.probeCurrent = model.FloatEnumerated(1.3e-9,
                          {0.1e-9, 1.3e-9, 2.6e-9, 3.4e-9, 11.564e-9, 23e-9},
                          unit="A")
        self.accelVoltage = model.FloatContinuous(10e6, (1e6, 30e6), unit="V")
Esempio n. 7
0
    def __init__(self, name, role, device=None, dependencies=None, children=None, daemon=None,
                 disc_volt=None, zero_cross=None, shutter_axes=None, **kwargs):
        """
        device (None or str): serial number (eg, 1020345) of the device to use
          or None if any device is fine.
        dependencies (dict str -> Component): shutters components (shutter0 and shutter1 are valid)
        children (dict str -> kwargs): the names of the detectors (detector0 and
         detector1 are valid)
        disc_volt (2 (0 <= float <= 0.8)): discriminator voltage for the APD 0 and 1 (in V)
        zero_cross (2 (0 <= float <= 2e-3)): zero cross voltage for the APD0 and 1 (in V)
        shutter_axes (dict str -> str, value, value): internal child role of the photo-detector ->
          axis name, position when shutter is closed (ie protected), position when opened (receiving light).
        """
        if dependencies is None:
            dependencies = {}
        if children is None:
            children = {}

        if device == "fake":
            device = None
            self._dll = FakePHDLL()
        else:
            self._dll = PHDLL()
        self._idx = self._openDevice(device)

        # Lock to be taken to avoid multi-threaded access to the hardware
        self._hw_access = threading.Lock()

        if disc_volt is None:
            disc_volt = [0, 0]
        if zero_cross is None:
            zero_cross = [0, 0]

        super(PH300, self).__init__(name, role, daemon=daemon, dependencies=dependencies, **kwargs)

        # TODO: metadata for indicating the range? cf WL_LIST?

        # TODO: do we need TTTR mode?
        self.Initialise(MODE_HIST)
        self._swVersion = self.GetLibraryVersion()
        self._metadata[model.MD_SW_VERSION] = self._swVersion
        mod, partnum, ver = self.GetHardwareInfo()
        sn = self.GetSerialNumber()
        self._hwVersion = "%s %s %s (s/n %s)" % (mod, partnum, ver, sn)
        self._metadata[model.MD_HW_VERSION] = self._hwVersion
        self._metadata[model.MD_DET_TYPE] = model.MD_DT_NORMAL

        logging.info("Opened device %d (%s s/n %s)", self._idx, mod, sn)

        self.Calibrate()

        # TODO: needs to be changeable?
        self.SetOffset(0)

        # To pass the raw count of each detector, we create children detectors.
        # It could also go into just separate DataFlow, but then it's difficult
        # to allow using these DataFlows in a standard way.
        self._detectors = {}
        self._shutters = {}
        self._shutter_axes = shutter_axes or {}
        for name, ckwargs in children.items():
            if name == "detector0":
                if "shutter0" in dependencies:
                    shutter_name = "shutter0"
                else:
                    shutter_name = None
                self._detectors[name] = PH300RawDetector(channel=0, parent=self, shutter_name=shutter_name, daemon=daemon, **ckwargs)
                self.children.value.add(self._detectors[name])
            elif name == "detector1":
                if "shutter1" in dependencies:
                    shutter_name = "shutter1"
                else:
                    shutter_name = None
                self._detectors[name] = PH300RawDetector(channel=1, parent=self, shutter_name=shutter_name, daemon=daemon, **ckwargs)
                self.children.value.add(self._detectors[name])
            else:
                raise ValueError("Child %s not recognized, should be detector0 or detector1.")
        for name, comp in dependencies.items():
            if name == "shutter0":
                if "shutter0" not in shutter_axes.keys():
                    raise ValueError("'shutter0' not found in shutter_axes")
                self._shutters['shutter0'] = comp
            elif name == "shutter1":
                if "shutter1" not in shutter_axes.keys():
                    raise ValueError("'shutter1' not found in shutter_axes")
                self._shutters['shutter1'] = comp
            else:
                raise ValueError("Dependency %s not recognized, should be shutter0 or shutter1.")

        # dwellTime = measurement duration
        dt_rng = (ACQTMIN * 1e-3, ACQTMAX * 1e-3)  # s
        self.dwellTime = model.FloatContinuous(1, dt_rng, unit="s")

        # Indicate first dim is time and second dim is (useless) X (in reversed order)
        self._metadata[model.MD_DIMS] = "XT"
        self._shape = (HISTCHAN, 1, 2**16) # Histogram is 32 bits, but only return 16 bits info

        # Set the CFD parameters (in mV)
        for i, (dv, zc) in enumerate(zip(disc_volt, zero_cross)):
            self.SetInputCFD(i, int(dv * 1000), int(zc * 1000))

        tresbase, bs = self.GetBaseResolution()
        tres = self.GetResolution()
        pxd_ch = {2 ** i * tresbase * 1e-12 for i in range(BINSTEPSMAX)}
        self.pixelDuration = model.FloatEnumerated(tres * 1e-12, pxd_ch, unit="s",
                                                   setter=self._setPixelDuration)

        res = self._shape[:2]
        self.resolution = model.ResolutionVA(res, (res, res), readonly=True)

        self.syncDiv = model.IntEnumerated(1, choices={1, 2, 4, 8}, unit="",
                                           setter=self._setSyncDiv)
        self._setSyncDiv(self.syncDiv.value)

        self.syncOffset = model.FloatContinuous(0, (SYNCOFFSMIN * 1e-12, SYNCOFFSMAX * 1e-12),
                                                unit="s", setter=self._setSyncOffset)

        # Make sure the device is synchronised and metadata is updated
        self._setSyncOffset(self.syncOffset.value)

        # Wrapper for the dataflow
        self.data = BasicDataFlow(self)
        # Note: Apparently, the hardware supports reading the data, while it's
        # still accumulating (ie, the acquisition is still running).
        # We don't support this feature for now, and if the user needs to see
        # the data building up, it shouldn't be costly (in terms of overhead or
        # noise) to just do multiple small acquisitions and do the accumulation
        # in software.
        # Alternatively, we could provide a second dataflow that sends the data
        # while it's building up.

        # Queue to control the acquisition thread:
        # * "S" to start
        # * "E" to end
        # * "T" to terminate
        self._genmsg = queue.Queue()
        self._generator = threading.Thread(target=self._acquire,
                                           name="PicoHarp300 acquisition thread")
        self._generator.start()
Esempio n. 8
0
    def __init__(self, name, role, parent, fov_range, **kwargs):
        # It will set up ._shape and .parent
        model.Emitter.__init__(self, name, role, parent=parent, **kwargs)

        self._shape = (2048, 2048)

        # This is the field of view when in Tescan Software magnification = 100
        # and working distance = 0,27 m (maximum WD of Mira TC). When working
        # distance is changed (for example when we focus) magnification mention
        # in odemis and Tescan software are expected to be different.
        self._hfw_nomag = 0.195565  # m

        # Get current field of view and compute magnification
        fov = self.parent._device.GetViewField() * 1e-03
        mag = self._hfw_nomag / fov

        # Field of view in Tescan is set in mm
        self.parent._device.SetViewField(self._hfw_nomag * 1e03 / mag)
        self.magnification = model.VigilantAttribute(mag,
                                                     unit="",
                                                     readonly=True)

        self.horizontalFOV = model.FloatContinuous(
            fov, range=fov_range, unit="m", setter=self._setHorizontalFOV)
        self.horizontalFOV.subscribe(
            self._onHorizontalFOV)  # to update metadata

        # pixelSize is the same as MD_PIXEL_SIZE, with scale == 1
        # == smallest size/ between two different ebeam positions
        pxs = (self._hfw_nomag / (self._shape[0] * mag),
               self._hfw_nomag / (self._shape[1] * mag))
        self.pixelSize = model.VigilantAttribute(pxs, unit="m", readonly=True)

        # (.resolution), .translation, .rotation, and .scaling are used to
        # define the conversion from coordinates to a region of interest.

        # (float, float) in px => moves center of acquisition by this amount
        # independent of scale and rotation.
        tran_rng = [(-self._shape[0] / 2, -self._shape[1] / 2),
                    (self._shape[0] / 2, self._shape[1] / 2)]
        self.translation = model.TupleContinuous((0, 0),
                                                 tran_rng,
                                                 cls=(int, long, float),
                                                 unit="",
                                                 setter=self._setTranslation)

        # .resolution is the number of pixels actually scanned. If it's less than
        # the whole possible area, it's centered.
        resolution = (self._shape[0] // 8, self._shape[1] // 8)
        self.resolution = model.ResolutionVA(resolution, [(1, 1), self._shape],
                                             setter=self._setResolution)
        self._resolution = resolution

        # (float, float) as a ratio => how big is a pixel, compared to pixelSize
        # it basically works the same as binning, but can be float
        # (Default to scan the whole area)
        self._scale = (self._shape[0] / resolution[0],
                       self._shape[1] / resolution[1])
        self.scale = model.TupleContinuous(self._scale, [(1, 1), self._shape],
                                           cls=(int, long, float),
                                           unit="",
                                           setter=self._setScale)
        self.scale.subscribe(self._onScale, init=True)  # to update metadata

        # (float) in rad => rotation of the image compared to the original axes
        # TODO: for now it's readonly because no rotation is supported
        self.rotation = model.FloatContinuous(0, [0, 2 * math.pi],
                                              unit="rad",
                                              readonly=True)

        self.dwellTime = model.FloatContinuous(1e-06, (1e-06, 1000), unit="s")
        self.dwellTime.subscribe(self._onDwellTime)

        # Range is according to min and max voltages accepted by Tescan API
        volt_range = self.GetVoltagesRange()
        volt = self.parent._device.HVGetVoltage()
        self.accelVoltage = model.FloatContinuous(volt, volt_range, unit="V")
        self.accelVoltage.subscribe(self._onVoltage)

        # 0 turns off the e-beam, 1 turns it on
        power_choices = set([0, 1])
        self._power = self.parent._device.HVGetBeam()  # Don't change state
        self.power = model.IntEnumerated(self._power,
                                         power_choices,
                                         unit="",
                                         setter=self._setPower)

        # Blanker is automatically enabled when no scanning takes place
        # TODO it may cause time overhead, check on testing => If so put some
        # small timeout (~ a few seconds) before blanking the beam.
        # self.parent._device.ScSetBlanker(0, 2)

        # Enumerated float with respect to the PC indexes of Tescan API
        self._list_currents = self.GetProbeCurrents()
        pc_choices = set(self._list_currents)
        # We use the current PC
        self._probeCurrent = self._list_currents[
            self.parent._device.GetPCIndex() - 1]
        self.probeCurrent = model.FloatEnumerated(self._probeCurrent,
                                                  pc_choices,
                                                  unit="A",
                                                  setter=self._setPC)
Esempio n. 9
0
    def __init__(self, name, role, parent, aperture=100e-6, wd=10e-3, **kwargs):
        """
        aperture (0 < float): aperture diameter of the electron lens
        wd (0 < float): working distance
        """
        # It will set up ._shape and .parent
        model.Emitter.__init__(self, name, role, parent=parent, **kwargs)
        self._aperture = aperture
        self._working_distance = wd

        fake_img = self.parent.fake_img
        if parent._drift_period:
            # half the size, to keep some margin for the drift
            self._shape = tuple(v // 2 for v in fake_img.shape[::-1])
        else:
            self._shape = fake_img.shape[::-1]

        # next two values are just to determine the pixel size
        # Distance between borders if magnification = 1. It should be found out
        # via calibration. We assume that image is square, i.e., VFV = HFV
        self._hfw_nomag = 0.25  # m

        # pixelSize is the same as MD_PIXEL_SIZE, with scale == 1
        # == smallest size/ between two different ebeam positions
        pxs = fake_img.metadata[model.MD_PIXEL_SIZE]
        self.pixelSize = model.VigilantAttribute(pxs, unit="m", readonly=True)

        # the horizontalFoV VA indicates that it's possible to control the zoom
        hfv = pxs[0] * self._shape[0]
        self.horizontalFoV = model.FloatContinuous(hfv, range=[10e-9, 10e-3],
                                                   unit="m")
        self.magnification = model.VigilantAttribute(self._hfw_nomag / hfv,
                                                     unit="", readonly=True)
        self.horizontalFoV.subscribe(self._onHFV)

        # To provide some rough idea of the step size when changing focus
        # Depends on the pixelSize, so will be updated whenever the HFW changes
        self.depthOfField = model.FloatContinuous(1e-6, range=(0, 1e9),
                                                  unit="m", readonly=True)
        self._updateDepthOfField()  # needs .pixelSize

        # (.resolution), .translation, .rotation, and .scaling are used to
        # define the conversion from coordinates to a region of interest.

        # (float, float) in m => physically moves the e-beam.
        shift_rng = ((-50e-06, -50e-06),
                    (50e-06, 50e-06))
        self.shift = model.TupleContinuous((0, 0), shift_rng,
                                              cls=(int, long, float), unit="m")

        # (float, float) in m => moves center of acquisition by this amount
        # independent of scale and rotation.
        tran_rng = [(-self._shape[0] / 2, -self._shape[1] / 2),
                    (self._shape[0] / 2, self._shape[1] / 2)]
        self.translation = model.TupleContinuous((0, 0), tran_rng,
                                              cls=(int, long, float), unit="px",
                                              setter=self._setTranslation)

        # .resolution is the number of pixels actually scanned. If it's less than
        # the whole possible area, it's centered.
        resolution = (self._shape[0] // 4, self._shape[1] // 4)
        self.resolution = model.ResolutionVA(resolution, [(1, 1), self._shape],
                                             setter=self._setResolution)
        self._resolution = resolution

        # (float, float) as a ratio => how big is a pixel, compared to pixelSize
        # it basically works the same as binning, but can be float
        # (Default to scan the whole area)
        self._scale = (self._shape[0] / resolution[0], self._shape[1] / resolution[1])
        self.scale = model.TupleContinuous(self._scale, [(1, 1), self._shape],
                                           cls=(int, long, float),
                                           unit="", setter=self._setScale)
        self.scale.subscribe(self._onScale, init=True) # to update metadata

        # (float) in rad => rotation of the image compared to the original axes
        self.rotation = model.FloatContinuous(0, [0, 2 * math.pi], unit="rad")

        self.dwellTime = model.FloatContinuous(1e-06, (1e-06, 1000), unit="s")

        # VAs to control the ebeam, purely fake
        self.probeCurrent = model.FloatEnumerated(1.3e-9,
                          {0.1e-9, 1.3e-9, 2.6e-9, 3.4e-9, 11.564e-9, 23e-9},
                          unit="A")
        self.accelVoltage = model.FloatContinuous(10e3, (1e3, 30e3), unit="V")

        # Pretend it's ready to acquire an image
        self.power = model.BooleanVA(True)
        # Blanker has a None = "auto" mode which automatically blanks when not scanning
        self.blanker = model.VAEnumerated(None, choices={True: 'blanked', False: 'unblanked', None: 'auto'})