Esempio n. 1
0
    def test_tc(self):
        """
        TupleContinuous
        """

        va = model.TupleContinuous((0.1, 10, .5),
                                   ((-1.3, 9, 0), (100., 150., 1.)),
                                   cls=(int, long, float))
        self.assertEqual(va.value, (0.1, 10, .5))
        self.assertEqual(va.range, ((-1.3, 9, 0), (100., 150., 1.)))

        # must convert anything to a tuple
        va.value = [-1, 150, .5]
        self.assertEqual(va.value, (-1, 150, .5))

        # must not accept values outside of the range
        try:
            va.value = (-1., 160., .5)
            self.fail("Assigning value not in range should not be allowed.")
        except IndexError:
            pass  # as it should be

        try:
            va.value = (10., 10.)
            self.fail(
                "Assigning a 2-tuple to a 3-tuple should not be allowed.")
        except TypeError:
            pass  # as it should be

        # Test creating a VA with value out of range
        with self.assertRaises(IndexError):
            va = model.TupleContinuous((0.1, 10), ((-1.3, 12), (100., 150.)),
                                       cls=(int, long, float))
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):
     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")
Esempio n. 4
0
    def __init__(self, name, coordinates, roc, asm, multibeam, descanner,
                 detector):
        """
        :param name: (str) Name of the region of acquisition (ROA). It is the name of the megafield (id) as stored on
                     the external storage.
        :param coordinates: (float, float, float, float) left, top, right, bottom, Bounding box
                            coordinates of the ROA in [m]. The coordinates are in the sample carrier coordinate
                            system, which corresponds to the component with role='stage'.
        :param roc: (FastEMROC) Corresponding region of calibration (ROC).
        :param asm: (technolution.AcquisitionServer) The acquisition server module component.
        :param multibeam: (technolution.EBeamScanner) The multibeam scanner component of the acquisition server module.
        :param descanner: (technolution.MirrorDescanner) The mirror descanner component of the acquisition server module.
        :param detector: (technolution.MPPC) The detector object to be used for collecting the image data.
        """
        self.name = model.StringVA(name)
        self.coordinates = model.TupleContinuous(coordinates,
                                                 range=((-1, -1, -1, -1),
                                                        (1, 1, 1, 1)),
                                                 cls=(int, float),
                                                 unit='m')
        self.roc = model.VigilantAttribute(roc)
        self._asm = asm
        self._multibeam = multibeam
        self._descanner = descanner
        self._detector = detector

        # List of tuples(int, int) containing the position indices of each field to be acquired.
        # Automatically updated when the coordinates change.
        self.field_indices = []
        self.coordinates.subscribe(self.on_coordinates, init=True)
Esempio n. 5
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()
Esempio n. 6
0
    def __init__(self, name, detector, dataflow, emitter, forcemd=None, **kwargs):
        """
        forcemd (None or dict of MD_* -> value): force the metadata of the
          .image DataArray to be overridden by this metadata.
        """
        super(LiveStream, self).__init__(name, detector, dataflow, emitter, **kwargs)

        self._forcemd = forcemd

        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._ht_needs_recompute = threading.Event()
        self._hthread = threading.Thread(target=self._histogram_thread,
                                         args=(weakref.ref(self),),
                                         name="Histogram computation")
        self._hthread.daemon = True
        self._hthread.start()

        self._prev_dur = None
        self._prep_future = model.InstantaneousFuture()
Esempio n. 7
0
    def __init__(self, scanner, detector):
        """
        :param scanner: (Emitter) A component with a .dwellTime, .translation, .scale.
        :param detector: (Detector) To acquire the signal.
        """
        super(AnchorDriftCorrector, self).__init__()
        self._scanner = scanner
        self._detector = detector
        self._dc_estimator = None
        self._period_acq = None  # number of acq left until next drift correction is performed

        # roi: the anchor region, it must be set to something different from
        #  UNDEFINED_ROI to run.
        # dwellTime: dwell time used when acquiring anchor region
        # period is the (approximate) time between two acquisition of the
        #  anchor (and drift compensation). The exact period is determined so
        #  that it fits with the region of acquisition.
        # Note: the scale used for the acquisition of the anchor region is
        #  selected by the AnchoredEstimator, to be as small as possible while
        #  still not scanning too many pixels.
        self.roi = model.TupleContinuous(UNDEFINED_ROI,
                                         range=((0, 0, 0, 0), (1, 1, 1, 1)),
                                         cls=(int, long, float),
                                         setter=self._setROI)
        self.dwellTime = model.FloatContinuous(scanner.dwellTime.range[0],
                                               range=scanner.dwellTime.range, unit="s")
        # in seconds, default to "fairly frequent" to work hopefully in most cases
        self.period = model.FloatContinuous(10, range=(0.1, 1e6), unit="s")
Esempio n. 8
0
    def __init__(self, name, detector, emitter, spectrograph, opm=None):
        """
        name (string): user-friendly name of this stream
        detector (Detector): the monochromator
        emitter (Emitter): the emitter (eg: ebeam scanner)
        spectrograph (Actuator): the spectrograph
        """
        self.name = model.StringVA(name)

        # Hardware Components
        self._detector = detector
        self._emitter = emitter
        self._sgr = spectrograph
        self._opm = opm

        self.is_active = model.BooleanVA(False)

        wlr = spectrograph.axes["wavelength"].range
        self.startWavelength = model.FloatContinuous(400e-9, wlr, unit="m")
        self.endWavelength = model.FloatContinuous(500e-9, wlr, unit="m")
        self.numberOfPixels = model.IntContinuous(51, (2, 10001), unit="px")
        # TODO: could be a local attribute?
        self.dwellTime = model.FloatContinuous(
            1e-3, range=self._emitter.dwellTime.range, unit="s")
        self.emtTranslation = model.TupleContinuous(
            (0, 0),
            range=self._emitter.translation.range,
            cls=(int, long, float),
            unit="px")

        # For acquisition
        self._pt_acq = threading.Event()
        self._data = []
        self._md = {}
Esempio n. 9
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
Esempio n. 10
0
    def __init__(self, name, detector, dataflow, emitter):
        self.name = model.StringVA(name)

        # Hardware Components
        self._detector = detector  # the spectrometer
        self._emitter = emitter  # the e-beam
        # To acquire simultaneously other detector (ex: SEM secondary electrons)
        # a separate stream must be used, and the acquisition manager will take
        # care of doing both at the same time

        # data-flow of the spectrometer
        self._dataflow = dataflow

        self.raw = []  # to contain data during acquisition (from MD streams)

        # all the information needed to acquire an image (in addition to the
        # hardware component settings which can be directly set).

        # ROI + repetition is sufficient, but pixel size is nicer for the user
        # and allow us to ensure each pixel is square. (Non-square pixels are
        # not a problem for the hardware, but annoying to display data in normal
        # software).

        # We ensure in the setters that all the data is always consistent:
        # roi set: roi + pxs → repetition + roi + pxs
        # pxs set: roi + pxs → repetition + roi (small changes)
        # repetition set: repetition + roi + pxs → repetition + pxs + roi (small changes)

        # 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),
                                         setter=self._setROI)
        # the number of pixels acquired in each dimension
        # it will be assigned to the resolution of the emitter (but cannot be
        # directly set, as one might want to use the emitter while configuring
        # the stream).
        self.repetition = model.ResolutionVA(emitter.resolution.value,
                                             emitter.resolution.range,
                                             setter=self._setRepetition)

        # the size of the pixel, horizontally and vertically
        # actual range is dynamic, as it changes with the magnification
        self.pixelSize = model.FloatContinuous(emitter.pixelSize.value[0],
                                               range=[0, 1],
                                               unit="m",
                                               setter=self._setPixelSize)

        # exposure time of each pixel is the exposure time of the detector,
        # the dwell time of the emitter will be adapted before acquisition.

        # Update the pixel size whenever SEM magnification changes
        # This allows to keep the ROI at the same place in the SEM FoV.
        # Note: this is to be done only if the user needs to manually update the
        # magnification.
        self._prev_mag = emitter.magnification.value
        emitter.magnification.subscribe(self._onMagnification)
Esempio n. 11
0
 def __init__(self, name, coordinates):
     """
     :param name: (str) name of the ROC
     :param coordinates: (float, float, float, float) l, t, r, b coordinates in m
     """
     self.name = model.StringVA(name)
     self.coordinates = model.TupleContinuous(coordinates,
                                              range=((-1, -1, -1, -1),
                                                     (1, 1, 1, 1)),
                                              cls=(int, float),
                                              unit='m')
     self.parameters = None  # calibration object with all relevant parameters
Esempio n. 12
0
 def __init__(self, name, coordinates, roc):
     """
     :param name: (str) name of the ROA
     :param coordinates: (float, float, float, float) l, t, r, b coordinates in m
     :param roc: (FastEMROC) corresponding region of calibration
     """
     self.name = model.StringVA(name)
     self.coordinates = model.TupleContinuous(coordinates,
                                              range=((-1, -1, -1, -1),
                                                     (1, 1, 1, 1)),
                                              cls=(int, float),
                                              unit='m')
     self.roc = model.VigilantAttribute(roc)
Esempio n. 13
0
    def __init__(self,
                 name,
                 coordinates,
                 roc_2,
                 roc_3,
                 asm,
                 multibeam,
                 descanner,
                 detector,
                 overlap=0,
                 pre_calibrate=False):
        """
        :param name: (str) Name of the region of acquisition (ROA). It is the name of the megafield (id) as stored on
                     the external storage.
        :param coordinates: (float, float, float, float) xmin, ymin, xmax, ymax
                            Bounding box coordinates of the ROA in [m]. The coordinates are in the sample carrier
                            coordinate system, which corresponds to the component with role='stage'.
        :param roc_2: (FastEMROC) Corresponding region of calibration (ROC). Used for dark offset and digital
                      gain calibration.
        :param roc_3: (FastEMROC) Corresponding region of calibration (ROC). Used for the final scanner rotation,
                      final scanning amplitude and cell translation (cell stitching) calibration (field corrections).
        :param asm: (technolution.AcquisitionServer) The acquisition server module component.
        :param multibeam: (technolution.EBeamScanner) The multibeam scanner component of the acquisition server module.
        :param descanner: (technolution.MirrorDescanner) The mirror descanner component of the acquisition server module.
        :param detector: (technolution.MPPC) The detector object to be used for collecting the image data.
        :param overlap: (float), optional
            The amount of overlap required between single fields. An overlap of 0.2 means that two neighboring fields
            overlap by 20%. By default, the overlap is 0, this means there is no overlap and one field is exactly next
            to the neighboring field.
        :param pre_calibrate: (bool) If True run pre-calibrations before each ROA acquisition.
        """
        self.name = model.StringVA(name)
        self.coordinates = model.TupleContinuous(coordinates,
                                                 range=((-1, -1, -1, -1),
                                                        (1, 1, 1, 1)),
                                                 cls=(int, float),
                                                 unit='m')
        self.roc_2 = model.VigilantAttribute(roc_2)
        self.roc_3 = model.VigilantAttribute(roc_3)
        self._asm = asm
        self._multibeam = multibeam
        self._descanner = descanner
        self._detector = detector

        # List of tuples(int, int) containing the position indices of each field to be acquired.
        # Automatically updated when the coordinates change.
        self.field_indices = []
        self.overlap = overlap
        self.pre_calibrate = pre_calibrate
        self.coordinates.subscribe(self.on_coordinates, init=True)
Esempio n. 14
0
    def __init__(self, name, detector, dataflow, emitter):
        Stream.__init__(self, name, detector, dataflow, emitter)

        # TODO: Anti-aliasing/Pixel fuzzing
        # .fuzzing: boolean
        # Might be better to automatically activate it for Spectrum, and disable
        # it for AR (without asking the user)

        try:
            self._prevDwellTime = emitter.dwellTime.value
            emitter.dwellTime.subscribe(self.onDwellTime)
        except AttributeError:
            # if emitter has no dwell time -> no problem
            pass

        # Actually use the ROI
        self.roi.subscribe(self._onROI)

        # Spot mode: when set (and stream is active), it will drive the e-beam
        # do only the center of the scanning area. Image is not updated.
        # TODO: is this the right interface? Shall we just have a different
        # stream type?
        self.spot = model.BooleanVA(False)

        # used to reset the previous settings after spot mode
        self._no_spot_settings = (None, None, None
                                  )  # dwell time, resolution, translation
        self.spot.subscribe(self._onSpot)

        # drift correction VAs:
        # dcRegion defines the anchor region, drift correction will be disabled
        #   if it is set to UNDEFINED_ROI
        # dcDwellTime: dwell time used when acquiring anchor region
        # dcPeriod is the (approximate) time between two acquisition of the
        #  anchor (and drift compensation). The exact period is determined so
        #  that it fits with the region of acquisition.
        # Note: the scale used for the acquisition of the anchor region is the
        #  same as the scale of the SEM. We could add a dcScale if it's needed.
        self.dcRegion = model.TupleContinuous(UNDEFINED_ROI,
                                              range=((0, 0, 0, 0), (1, 1, 1,
                                                                    1)),
                                              cls=(int, long, float),
                                              setter=self._setDCRegion)
        self.dcDwellTime = model.FloatContinuous(emitter.dwellTime.range[0],
                                                 range=emitter.dwellTime.range,
                                                 unit="s")
        self.dcPeriod = model.FloatContinuous(
            10,  # s, default to "fairly frequent" to work hopefully in most cases
            range=[0.1, 1e6],
            unit="s")
Esempio n. 15
0
 def __init__(self, name, coordinates):
     """
     :param name: (str) Name of the region of calibration (ROC). It is the name of the megafield (id) as stored on
                  the external storage.
     :param coordinates: (float, float, float, float) left, top, right, bottom, Bounding box coordinates of the
                         ROC in [m]. The coordinates are in the sample carrier coordinate system, which
                         corresponds to the component with role='stage'.
     """
     self.name = model.StringVA(name)
     self.coordinates = model.TupleContinuous(coordinates,
                                              range=((-1, -1, -1, -1),
                                                     (1, 1, 1, 1)),
                                              cls=(int, float),
                                              unit='m')
     self.parameters = None  # calibration object with all relevant parameters
Esempio n. 16
0
    def __init__(self, testCase, align):
        super(FakeCCD, self).__init__("testccd", "ccd")
        self.testCase = testCase
        self.align = align
        self.exposureTime = model.FloatContinuous(1, (1e-6, 1000), unit="s")
        self.binning = model.TupleContinuous((1, 1), [(1, 1), (8, 8)],
                                       cls=(int, long, float), unit="")
        self.resolution = model.ResolutionVA((2160, 2560), [(1, 1), (2160, 2560)])

        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 = self.testCase.fake_img
Esempio n. 17
0
    def __init__(self, stream):
        '''
        stream (Stream): the Stream to project
        '''
        super(RGBSpatialProjection, self).__init__(stream)

        self.should_update = model.BooleanVA(False)
        self.name = stream.name
        self.image = model.VigilantAttribute(None)

        # Don't call at init, so don't set metadata if default value
        self.stream.tint.subscribe(self._onTint)
        self.stream.intensityRange.subscribe(self._onIntensityRange)
        self.stream.auto_bc.subscribe(self._onAutoBC)
        self.stream.auto_bc_outliers.subscribe(self._onOutliers)

        if hasattr(stream, '_das'):
            raw = stream._das
            md = raw.metadata
            # get the pixel size of the full image
            ps = md[model.MD_PIXEL_SIZE]
            max_mpp = ps[0] * (2**raw.maxzoom)
            # sets the mpp as the X axis of the pixel size of the full image
            mpp_rng = (ps[0], max_mpp)
            self.mpp = model.FloatContinuous(max_mpp,
                                             mpp_rng,
                                             setter=self._set_mpp)

            full_rect = img._getBoundingBox(raw)
            l, t, r, b = full_rect
            rect_range = ((l, b, l, b), (r, t, r, t))
            self.rect = model.TupleContinuous(full_rect, rect_range)

            self.mpp.subscribe(self._onMpp)
            self.rect.subscribe(self._onRect)

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

            # When True, the projected tiles cache should be invalidated
            self._projectedTilesInvalid = True

        self._shouldUpdateImage()
Esempio n. 18
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
        self.binning = model.TupleContinuous((1, 1), [(1, 1), (8, 8)],
                                             cls=(int, long, float),
                                             unit="")
        self.resolution = model.ResolutionVA(res, [(1, 1), res])

        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
Esempio n. 19
0
    def __init__(self,
                 name,
                 role,
                 port=None,
                 serialnum=None,
                 dependencies=None,
                 **kwargs):
        """
        :param port (str): (e.g. "/dev/ttyUSB0") or pattern for port ("/dev/ttyUSB*"),
            "/dev/fake" will start the simulator
        :param serialnum (str): serial number of RS485 adapter
            The connection can be specified by either port or serialnum, it's not needed to provide both.
        :param dependencies (dict str -> scanner):
            scanner component -> name of the xt multibeam scanner component. If None, no calibration
            data, which is specific for the multibeam system, will be retrieved from the scanner and
            added to the beamshift metadata.
        """
        # .hwVersion, .swVersion not available
        model.HwComponent.__init__(self, name, role, **kwargs)

        # Find port by RS485 adapter serial number
        self._portpattern = port
        self._serialnum = serialnum
        self._port = self._findDevice(port, serialnum)
        self._serial = self._openSerialPort(self._port)

        # Shift VA
        # Range depends on metadata and will be checked in ._write_registers
        # The value is not correct until the metadata is set.
        self.shift = model.TupleContinuous((0, 0),
                                           range=((-1, -1), (1, 1)),
                                           cls=(int, float),
                                           unit="m",
                                           setter=self._setShift)

        if dependencies and "scanner" in dependencies.keys():
            self.updateMetadata({
                model.MD_CALIB:
                dependencies["scanner"].beamShiftTransformationMatrix.value
            })
Esempio n. 20
0
    def __init__(self, name, detector, dataflow, emitter, **kwargs):
        super(SEMStream, self).__init__(name, detector, dataflow, emitter, **kwargs)

        # To restart directly acquisition if settings change
        try:
            self._getEmitterVA("dwellTime").subscribe(self._onDwellTime)
        except AttributeError:
            # if emitter has no dwell time -> no problem
            pass
        try:
            # Resolution picks up also scale and ROI change
            self._getEmitterVA("resolution").subscribe(self._onResolution)
        except AttributeError:
            pass

        # Actually use the ROI
        self.roi.subscribe(self._onROI)

        # drift correction VAs:
        # Not currently supported by this standard stream, but some synchronised
        #   streams do.
        # dcRegion defines the anchor region, drift correction will be disabled
        #   if it is set to UNDEFINED_ROI
        # dcDwellTime: dwell time used when acquiring anchor region
        # dcPeriod is the (approximate) time between two acquisition of the
        #  anchor (and drift compensation). The exact period is determined so
        #  that it fits with the region of acquisition.
        # Note: the scale used for the acquisition of the anchor region is the
        #  same as the scale of the SEM. We could add a dcScale if it's needed.
        self.dcRegion = model.TupleContinuous(UNDEFINED_ROI,
                                              range=((0, 0, 0, 0), (1, 1, 1, 1)),
                                              cls=(int, long, float),
                                              setter=self._setDCRegion)
        self.dcDwellTime = model.FloatContinuous(emitter.dwellTime.range[0],
                                                 range=emitter.dwellTime.range, unit="s")
        # in seconds, default to "fairly frequent" to work hopefully in most cases
        self.dcPeriod = model.FloatContinuous(10, range=(0.1, 1e6), unit="s")
Esempio n. 21
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])
Esempio n. 22
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'})
Esempio n. 23
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)]
Esempio n. 24
0
    def __init__(self, name, detector, dataflow, emitter, **kwargs):
        """
        :param detector: (DigitalCamera) The optical detector (ccd).
        :param dataflow: (DataFlow) The dataflow of the detector.
        :param emitter: (Emitter) The component that generates energy and
                        also controls the position of the energy (the e-beam of the SEM).
        """
        if "acq_type" not in kwargs:
            kwargs["acq_type"] = model.MD_AT_CL

        # Skip the RepetitionStream.__init__ because it gets confused with pixelSize being
        # two floats.
        acqstream.LiveStream.__init__(self, name, detector, dataflow, emitter)

        self._scanner = emitter

        # Region of acquisition (ROI) + repetition is sufficient, but pixel size is nicer for the user.
        # As the settings are over-specified, whenever ROI, repetition, or pixel
        # size changes, one (or more) other VA is updated to keep everything
        # consistent. In addition, there are also hardware constraints, which
        # must also be satisfied. The main rules followed are:
        #  * Try to keep the VA which was changed (by the user) as close as
        #    possible to the requested value (within hardware limits).
        # So in practice, the three setters behave in this way:
        #  * region of acquisition set: ROI (as requested) + repetition (current) → PxS (updated)
        #  * pixel size set: PxS (as requested) + ROI (current) → repetition (updated)
        #    The ROA is adjusted to ensure the repetition is a round number and acceptable by the hardware.
        #  * repetition set: Rep (as requested) + ROI (current) → PxS (updated)
        #    The repetition is adjusted to fit the hardware limits

        # Region of interest as left, top, right, bottom (in ratio from the
        # whole area of the emitter => between 0 and 1)
        # We overwrite the VA provided by LiveStream to define a setter.
        self.roi = model.TupleContinuous((0, 0, 1, 1),
                                         range=((0, 0, 0, 0), (1, 1, 1, 1)),
                                         cls=(int, float),
                                         setter=self._setROI)

        # Start with pixel size to fit 1024 px, as it's typically a sane value
        # for the user (and adjust for the hardware).
        spxs = emitter.pixelSize.value  # m, size at scale = 1
        sshape = emitter.shape  # px, max number of pixels scanned
        phy_size_x = spxs[0] * sshape[0]  # m
        phy_size_y = spxs[1] * sshape[1]  # m
        pxs = (phy_size_x / 1024, phy_size_y / 1024)

        roi, rep, pxs = self._updateROIAndPixelSize(self.roi.value, pxs)

        # The number of pixels acquired in each dimension. It will be assigned to the resolution
        # of the emitter (but cannot be directly set, as one might want to use the emitter while
        # configuring the stream).
        self.repetition = model.ResolutionVA(rep,
                                             emitter.resolution.range,
                                             setter=self._setRepetition)

        # The size of the pixel (IOW, the distance between the center of two
        # consecutive pixels or the "pitch"). Value can vary for vertical and horizontal direction.
        # The actual range is dynamic, as it changes with the magnification.
        self.pixelSize = model.TupleContinuous(pxs,
                                               range=((0, 0), (1, 1)),
                                               unit="m",
                                               cls=(int, float),
                                               setter=self._setPixelSize)

        # Typical user wants density much lower than SEM.
        self.pixelSize.value = tuple(numpy.array(self.pixelSize.value) * 50)

        # Maximum margin is half the CCD FoV.
        ccd_rect = get_ccd_fov(detector)
        max_margin = max(ccd_rect[2] - ccd_rect[0],
                         ccd_rect[3] - ccd_rect[1]) / 2
        # roi_margin (0 <= float): extra margin (in m) around the SEM area to select the CCD ROI.
        self.roi_margin = model.FloatContinuous(0, (0, max_margin), unit="m")

        # Exposure time of each pixel is the exposure time of the detector.
        # The dwell time of the emitter will be adapted before the acquisition.

        # Update the pixel size whenever SEM magnification changes.
        # This allows to keep the ROI at the same place in the SEM FoV.
        # Note: This is to be done only if the user needs to manually update the magnification.
        self.magnification = self._scanner.magnification
        self._prev_mag = self.magnification.value
        self.magnification.subscribe(self._onMagnification)
Esempio n. 25
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. 26
0
    def __init__(self,
                 name,
                 detector,
                 sed,
                 emitter,
                 spectrograph,
                 lens_switch,
                 bigslit,
                 opm,
                 wl_inverted=False):
        """
        name (string): user-friendly name of this stream
        detector (Detector): the 2D CCD which get wavelength on the X axis and angles on the Y axis
        sed (Detector): the se-detector
        emitter (Emitter): the emitter (eg: ebeam scanner)
        spectrograph (Actuator): the spectrograph
        wl_inverted (bool): if True, will swap the wavelength axis of the CCD, in
          order to support hardware where the highest wavelengths are at the smallest
          indices. (The MD_WL_LIST is *not* inverted)
        """
        self.name = model.StringVA(name)

        # Hardware Components
        self._detector = detector
        self._sed = sed
        self._emitter = emitter
        self._sgr = spectrograph
        self._opm = opm
        self._lsw = lens_switch
        self._bigslit = bigslit
        self._wl_inverted = wl_inverted

        wlr = spectrograph.axes["wavelength"].range
        slitw = spectrograph.axes["slit-in"].range
        self.centerWavelength = model.FloatContinuous(500e-9, wlr, unit="m")
        self.slitWidth = model.FloatContinuous(100e-6, slitw, unit="m")
        # dwell time and exposure time are the same thing in this case
        self.dwellTime = model.FloatContinuous(
            1, range=detector.exposureTime.range, unit="s")
        self.emtTranslation = model.TupleContinuous(
            (0, 0),
            range=self._emitter.translation.range,
            cls=(int, long, float),
            unit="px")

        # 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))

        # 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")

        #self.binning = model.VAEnumerated((1,1), choices=set([(1,1), (2,2), (2,3)]))
        # separate binning values because it can useful for experiment
        self.binninghorz = model.VAEnumerated(1, choices={1, 2, 4, 8, 16})
        self.binningvert = model.VAEnumerated(1, choices={1, 2, 4, 8, 16})
        self.nDC = model.IntContinuous(1, (1, 20))

        # For acquisition
        self.ARspectral_data = None
        self.ARspectral_data_received = threading.Event()
        self.sem_data = []
        self.sem_data_received = threading.Event()
        self._hw_settings = None
Esempio n. 27
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()
Esempio n. 28
0
    def __init__(self, name, role, parent, hfw_nomag, **kwargs):
        model.Emitter.__init__(self, name, role, parent=parent, **kwargs)
        self._hfw_nomag = hfw_nomag

        dwell_time_info = self.parent.dwell_time_info()
        self.dwellTime = model.FloatContinuous(self.parent.get_dwell_time(),
                                               dwell_time_info["range"],
                                               unit=dwell_time_info["unit"],
                                               setter=self._setDwellTime)

        voltage_info = self.parent.ht_voltage_info()
        self.accelVoltage = model.FloatContinuous(self.parent.get_ht_voltage(),
                                                  voltage_info["range"],
                                                  unit=voltage_info["unit"],
                                                  setter=self._setVoltage)

        self.blanker = model.BooleanVA(self.parent.beam_is_blanked(),
                                       setter=self._setBlanker)

        spotsize_info = self.parent.spotsize_info()
        self.spotSize = model.FloatContinuous(self.parent.get_ebeam_spotsize(),
                                              spotsize_info["range"],
                                              unit=spotsize_info["unit"],
                                              setter=self._setSpotSize)

        beam_shift_info = self.parent.beam_shift_info()
        range_x = beam_shift_info["range"]["x"]
        range_y = beam_shift_info["range"]["y"]
        self.beamShift = model.TupleContinuous(self.parent.get_beam_shift(),
                                               ((range_x[0], range_y[0]),
                                                (range_x[1], range_y[1])),
                                               cls=(int, float),
                                               unit=beam_shift_info["unit"],
                                               setter=self._setBeamShift)

        rotation_info = self.parent.rotation_info()
        self.rotation = model.FloatContinuous(self.parent.get_rotation(),
                                              rotation_info["range"],
                                              unit=rotation_info["unit"],
                                              setter=self._setRotation)

        scanning_size_info = self.parent.scanning_size_info()
        fov = self.parent.get_scanning_size()[0]
        self.horizontalFoV = model.FloatContinuous(
            fov,
            unit=scanning_size_info["unit"],
            range=scanning_size_info["range"]["x"],
            setter=self._setHorizontalFoV)

        mag = self._hfw_nomag / fov
        mag_range_max = self._hfw_nomag / scanning_size_info["range"]["x"][0]
        mag_range_min = self._hfw_nomag / scanning_size_info["range"]["x"][1]
        self.magnification = model.FloatContinuous(mag,
                                                   unit="",
                                                   range=(mag_range_min,
                                                          mag_range_max),
                                                   readonly=True)
        # To provide some rough idea of the step size when changing focus
        # Depends on the pixelSize, so will be updated whenever the HFW changes
        self.depthOfField = model.FloatContinuous(1e-6,
                                                  range=(0, 1e3),
                                                  unit="m",
                                                  readonly=True)
        self._updateDepthOfField()

        # Refresh regularly the values, from the hardware, starting from now
        self._updateSettings()
        self._va_poll = util.RepeatingTimer(5, self._updateSettings,
                                            "Settings polling")
        self._va_poll.start()
Esempio n. 29
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. 30
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_*