Example #1
0
    def test_resolution(self):
        va = model.ResolutionVA((10, 10), ((1, 1), (100, 150)))
        self.assertEqual(va.value, (10, 10))
        self.assertEqual(va.range, ((1, 1), (100, 150)))

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

        # Numpy integers are also fine
        va.value = (numpy.prod([10, 3]), numpy.uint64(1))

        # must not accept resolutions with float
        try:
            va.value = (8., 160)
            self.fail("Assigning non int values should not be allowed.")
        except TypeError:
            pass  # as it should be

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

        try:
            va.value = (10, 10, 10)
            self.fail(
                "Assigning a 3-tuple to a resolution should not be allowed.")
        except TypeError:
            pass  # as it should be
Example #2
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
Example #3
0
    def __init__(self, name, role, mag, pole_pos=None, **kwargs):
        """
        name (string): should be the name of the product (for metadata)
        mag (float > 0): magnification ratio
        pole_pos (2 floats > 0): position of the pole on the CCD (in px, without
          binning). Used for angular resolved imaging on SPARC (only).
          cf MD_AR_POLE
        """
        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
        self.magnification = model.FloatContinuous(mag,
                                                   range=[1e-3, 1e6],
                                                   unit="")

        if pole_pos is not None:
            if (not isinstance(pole_pos, collections.Iterable)
                    or len(pole_pos) != 2 or any(v < 0 for v in pole_pos)):
                raise ValueError("pole_pos must be 2 positive values, got %s" %
                                 pole_pos)
            self.polePosition = model.ResolutionVA(pole_pos,
                                                   range=[(0, 0), (1e6, 1e6)])
Example #4
0
    def __init__(self, name, role, parent, **kwargs):
        """
        Initialises the device.
        Raise an exception if the device cannot be opened.
        """
        model.DigitalCamera.__init__(self, name, role, parent=parent, **kwargs)

        # Parameters explanation: Chamber camera enabled on channel 0 (reserved),
        # without zoom applied so we get the maximum size of the image (1),
        # in the maximum fps (5), and compression mode 0 (must be so, according,
        # to Tescan API documentation).
        self.parent._device.CameraEnable(0, 1, 5, 0)
        # Wait for camera to be enabled
        while (self.parent._device.CameraGetStatus(0))[0] != 1:
            time.sleep(0.5)
        # Get a first image to determine the resolution
        width, height, img_str = self.parent._device.FetchCameraImage(0)
        self.parent._device.CameraDisable()
        resolution = (height, width)
        self._shape = resolution + (2**8, )
        self.resolution = model.ResolutionVA(resolution, [(1, 1),
                                                          (2048, 2048)],
                                             readonly=True)

        self.acquisition_lock = threading.Lock()
        self.acquire_must_stop = threading.Event()
        self.acquire_thread = None

        self.data = ChamberDataFlow(self)

        logging.debug("Camera component ready to use.")
Example #5
0
    def __init__(self, name, ccd, emitter, emd):
        """
        name (string): user-friendly name of this stream
        ccd (Camera): the ccd
        emitter (Emitter): the emitter (eg: ebeam scanner)
        emd (Detector): the SEM detector (eg: SED)
        """
        self.name = model.StringVA(name)

        # Hardware Components
        self._detector = emd
        self._emitter = emitter
        self._ccd = ccd

        # 0.1s is a bit small, but the algorithm will automaticaly try with
        # longer dwell times if no spot is visible first.
        self.dwellTime = model.FloatContinuous(0.1,
                                               range=emitter.dwellTime.range,
                                               unit="s")
        # The number of points in the grid
        self.repetition = model.ResolutionVA(
            (4, 4),  # good default
            range=((2, 2), (16, 16)))

        # Future generated by find_overlay
        self._overlay_future = None
Example #6
0
 def __init__(self, name):
     model.Emitter.__init__(self, name, "fakeebeam", parent=None)
     self._shape = (2048, 2048)
     self.resolution = model.ResolutionVA((256, 256), [(1, 1), self._shape])
     self.pixelSize = model.VigilantAttribute((1e-9, 1e-9),
                                              unit="m",
                                              readonly=True)
     self.magnification = model.FloatVA(1000.)
Example #7
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)
Example #8
0
    def __init__(self, name, role, parent, **kwargs):
        '''
        parent (symphotime.Controller): a symphotime server parent object
        '''
        # we will fill the set of children with Components later in ._children
        model.Emitter.__init__(self, name, role, parent=parent, **kwargs)
        self._shape = (2048, 2048) # Max resolution

        # Define VA's as references to the parent.
        self.filename = model.StringVA(setter=self._setFilename)
        self.directory = model.StringVA(setter=self._setDirectory)
        self.resolution = model.ResolutionVA((64, 64), ((1, 1), (2048, 2048)))
        self.bidirectional = model.BooleanVA(value=False)
        self.dwellTime = model.FloatContinuous(value=10e-6, range=DWELLTIME_RNG, unit="s")
Example #9
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")
    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
Example #11
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
Example #12
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")
Example #13
0
    def __init__(self, name, role, children=None, daemon=None, **kwargs):
        """
        All the arguments are identical to AndorCam2, expected: 
        children (dict string->kwargs): name of child must be "shamrock" and the
          kwargs contains the arguments passed to instantiate the Shamrock component
        """
        # we will fill the set of children with Components later in ._children
        model.Detector.__init__(self, name, role, daemon=daemon, **kwargs)

        # Create the detector (ccd) child
        try:
            dt_kwargs = children["andorcam2"]
        except Exception:
            raise ValueError("AndorSpec excepts one child named 'andorcam2'")

        # We could inherit from it, but difficult to not mix up .binning, .shape
        # .resolution...
        self._detector = andorcam2.AndorCam2(daemon=daemon, **dt_kwargs)
        self._children.add(self._detector)
        dt = self._detector

        # Copy and adapt the VAs and roattributes from the detector
        # set up the detector part
        # check that the shape is "horizontal"
        if dt.shape[0] <= 1:
            raise ValueError(
                "Child detector must have at least 2 pixels horizontally")
        if dt.shape[0] < dt.shape[1]:
            logging.warning(
                "Child detector is shaped vertically (%dx%d), "
                "this is probably incorrect, as wavelengths are "
                "expected to be along the horizontal axis", dt.shape[0],
                dt.shape[1])
        # shape is same as detector (raw sensor), but the max resolution is always flat
        self._shape = tuple(dt.shape)  # duplicate

        # The resolution and binning are derived from the detector, but with
        # settings set so that there is only one horizontal line.
        if dt.binning.range[1][1] < dt.resolution.range[1][1]:
            # without software binning, we are stuck to the max binning
            logging.info(
                "Spectrometer %s will only use a %d px band of the %d "
                "px of the sensor", name, dt.binning.range[1][1],
                dt.resolution.range[1][1])

        resolution = (dt.resolution.range[1][0], 1)  # max,1
        # vertically: 1, with binning as big as possible
        binning = (dt.binning.value[0],
                   min(dt.binning.range[1][1], dt.resolution.range[1][1]))

        min_res = (dt.resolution.range[0][0], 1)
        max_res = (dt.resolution.range[1][0], 1)
        self.resolution = model.ResolutionVA(resolution, [min_res, max_res],
                                             setter=self._setResolution)
        # 2D binning is like a "small resolution"
        self._binning = binning
        self.binning = model.ResolutionVA(self._binning,
                                          dt.binning.range,
                                          setter=self._setBinning)

        self._setBinning(binning)  # will also update the resolution

        # TODO: update also the metadata MD_SENSOR_PIXEL_SIZE
        pxs = dt.pixelSize.value[
            0], dt.pixelSize.value[1] * dt.binning.value[1]
        self.pixelSize = model.VigilantAttribute(pxs, unit="m", readonly=True)
        # Note: the metadata has no MD_PIXEL_SIZE, but a MD_WL_LIST

        # TODO: support software binning by rolling up our own dataflow that
        # does data merging
        assert dt.resolution.range[0][1] == 1
        self.data = dt.data

        # duplicate every other VA and Event from the detector
        # that includes required VAs like .exposureTime
        for aname, value in model.getVAs(dt).items() + model.getEvents(
                dt).items():
            if not hasattr(self, aname):
                setattr(self, aname, value)
            else:
                logging.debug(
                    "skipping duplication of already existing VA '%s'", aname)

        assert hasattr(self, "exposureTime")

        # Create the spectrograph (actuator) child
        try:
            sp_kwargs = children["shamrock"]
        except Exception:
            raise ValueError("AndorSpec excepts one child named 'shamrock'")

        self._spectrograph = Shamrock(parent=self,
                                      path=self._detector._initpath,
                                      daemon=daemon,
                                      **sp_kwargs)
        self._children.add(self._spectrograph)

        self._spectrograph.position.subscribe(self._onPositionUpdate)
        self.resolution.subscribe(self._onResBinningUpdate)
        self.binning.subscribe(self._onResBinningUpdate, init=True)
Example #14
0
    def __init__(self,
                 name,
                 role,
                 device=None,
                 children=None,
                 daemon=None,
                 disc_volt=None,
                 zero_cross=None,
                 **kwargs):
        """
        device (None or str): serial number (eg, 1020345) of the device to use
          or None if any device is fine.
        children (dict str -> kwargs): the names of the detectors (detector0 and
         detector1 are valid) to the arguments.
        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)
        """
        if children is None:
            children = {}

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

        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, **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()

        # Do basic set-up for things that should never be needed to change
        self.SetSyncDiv(1)  # 1 = no divider TODO: needs to be a VA?

        # 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 = {}
        for name, ckwargs in children.items():
            if name == "detector0":
                i = 0
            elif name == "detector1":
                i = 1
            else:
                raise ValueError("")
            self._detectors[name] = PH300RawDetector(channel=i,
                                                     parent=self,
                                                     daemon=daemon,
                                                     **ckwargs)
            self.children.value.add(self._detectors[name])

        # 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_rng = (tresbase * 1e-12, 2**(BINSTEPSMAX - 1) * tresbase * 1e-12)
        self.pixelDuration = model.FloatContinuous(
            tres, pxd_rng, unit="s", setter=self._setPixelDuration)
        self._metadata[model.MD_PIXEL_DUR] = tres

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

        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()
Example #15
0
    def __init__(self, name, role, dependencies, **kwargs):
        '''
        dependencies (dict string->model.HwComponent): the dependencies
            There must be exactly two dependencies "spectrograph" and "detector". The
            first dimension of the CCD is supposed to be along the wavelength,
            with the first pixels representing the lowest wavelengths.
        Raise:
          ValueError: if the dependencies are not compatible
        '''
        # we will fill the set of dependencies with Components later in ._dependencies
        model.Detector.__init__(self,
                                name,
                                role,
                                dependencies=dependencies,
                                **kwargs)

        # Check the dependencies
        dt = dependencies["detector"]
        if not isinstance(dt, ComponentBase):
            raise ValueError("Dependency detector is not a component.")
        if ((not hasattr(dt, "shape") or not isinstance(dt.shape, tuple))
                or not model.hasVA(dt, "pixelSize")):
            raise ValueError(
                "Dependency detector is not a Detector component.")
        if not hasattr(dt, "data") or not isinstance(dt.data, DataFlowBase):
            raise ValueError("Dependency detector has not .data DataFlow.")
        self._detector = dt
        self.dependencies.value.add(dt)

        sp = dependencies["spectrograph"]
        if not isinstance(sp, ComponentBase):
            raise ValueError("Dependency spectrograph is not a component.")
        try:
            if "wavelength" not in sp.axes:
                raise ValueError(
                    "Dependency spectrograph has no 'wavelength' axis.")
        except Exception:
            raise ValueError("Dependency spectrograph is not an Actuator.")
        self._spectrograph = sp
        self.dependencies.value.add(sp)

        # set up the detector part
        # check that the shape is "horizontal"
        if dt.shape[0] <= 1:
            raise ValueError(
                "Dependency detector must have at least 2 pixels horizontally")
        if dt.shape[0] < dt.shape[1]:
            logging.warning(
                "Dependency detector is shaped vertically (%dx%d), "
                "this is probably incorrect, as wavelengths are "
                "expected to be along the horizontal axis", dt.shape[0],
                dt.shape[1])
        # shape is same as detector (raw sensor), but the max resolution is always flat
        self._shape = tuple(dt.shape)  # duplicate

        # Wrapper for the dataflow
        self.data = SpecDataFlow(self, dt.data)

        # The resolution and binning are derived from the detector, but with
        # settings set so that there is only one horizontal line.
        # So max vertical resolution is always 1.
        assert dt.resolution.range[0][1] == 1
        resolution = (dt.resolution.range[1][0], 1)  # max,1
        min_res = (dt.resolution.range[0][0], 1)
        max_res = (dt.resolution.range[1][0], 1)
        self.resolution = model.ResolutionVA(resolution, (min_res, max_res),
                                             setter=self._setResolution)

        # The vertical binning is linked to the detector resolution, as it
        # represents how many pixels are used (and binned). So the maximum
        # binning is the maximum *resolution* (and it's converted either to
        # detector binning, or binned later in software).
        min_bin = (dt.binning.range[0][0], dt.binning.range[0][1])
        max_bin = (dt.binning.range[1][0], dt.resolution.range[1][1])
        # Initial binning is minimum binning horizontally, and maximum vertically
        self._binning = (1, dt.resolution.range[1][1])
        self._max_det_vbin = dt.binning.range[1][1]
        self.binning = model.ResolutionVA(self._binning, (min_bin, max_bin),
                                          setter=self._setBinning)
        self._setBinning(self._binning)  # will also update the resolution

        # TODO: also wrap translation, if it exists?

        # duplicate every other VA and Event from the detector
        # that includes required VAs like .pixelSize and .exposureTime
        for aname, value in chain(
                model.getVAs(dt).items(),
                model.getEvents(dt).items()):
            if not hasattr(self, aname):
                setattr(self, aname, value)
            else:
                logging.debug(
                    "skipping duplication of already existing VA '%s'", aname)

        assert hasattr(self, "pixelSize")
        if not model.hasVA(self, "exposureTime"):
            logging.warning("Spectrometer %s has no exposureTime VA", name)

        sp.position.subscribe(self._onPositionUpdate)
        self.resolution.subscribe(self._onResBinning)
        self.binning.subscribe(self._onResBinning)
        self._updateWavelengthList()
Example #16
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")
Example #17
0
    def __init__(self,
                 name,
                 role,
                 image,
                 dependencies=None,
                 daemon=None,
                 blur_factor=1e4,
                 max_res=None,
                 **kwargs):
        """
        dependencies (dict string->Component): If "focus" is passed, and it's an
            actuator with a z axis, the image will be blurred based on the
            position, to simulate a focus axis.
        image (str or None): path to a file to use as fake image (relative to the directory of this class)
        max_res (tuple of (int, int) or None): maximum resolution to clip simulated image, if None whole image shape
            will be used. The simulated image will be a part of the original image based on the MD_POS metadata.
        """
        # TODO: support transpose? If not, warn that it's not accepted
        # fake image setup
        image = str(image)
        # ensure relative path is from this file
        if not os.path.isabs(image):
            image = os.path.join(os.path.dirname(__file__), image)
        converter = dataio.find_fittest_converter(image, mode=os.O_RDONLY)
        self._img = converter.read_data(image)[0]  # can be RGB or greyscale

        model.DigitalCamera.__init__(self,
                                     name,
                                     role,
                                     dependencies=dependencies,
                                     daemon=daemon,
                                     **kwargs)

        if self._img.ndim > 3:  # remove dims of length 1
            self._img = numpy.squeeze(self._img)

        imshp = self._img.shape
        if len(imshp) == 3 and imshp[0] in {3, 4}:
            # CYX, change it to YXC, to simulate a RGB detector
            self._img = numpy.rollaxis(self._img, 2)  # XCY
            self._img = numpy.rollaxis(self._img, 2)  # YXC
            imshp = self._img.shape

        def clip_max_res(img_res):
            if len(max_res) != 2:
                raise ValueError("Shape of max_res should be = 2.")
            return tuple(min(x, y) for x, y in zip(
                img_res, max_res))  # in case max_res > image shape

        # For RGB, the colour is last dim, but we still indicate it as higher
        # dimension to ensure shape always starts with X, Y
        if len(imshp) == 3 and imshp[-1] in {3, 4}:
            # resolution doesn't affect RGB dim
            res = imshp[-2::-1]
            self._img_res = res  # Original image shape in case it's clipped
            if max_res:
                res = clip_max_res(res)
            self._shape = res + imshp[-1::]  # X, Y, C
            # indicate it's RGB pixel-per-pixel ordered
            self._img.metadata[model.MD_DIMS] = "YXC"
        else:
            self._img_res = imshp[::
                                  -1]  # Original image shape in case it's clipped
            res = imshp[::-1] if max_res is None else tuple(max_res)
            if max_res:
                res = clip_max_res(res)
            self._shape = res  # X, Y,...
        # TODO: handle non integer dtypes
        depth = 2**(self._img.dtype.itemsize * 8)
        self._shape += (depth, )

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

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

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

        self._orig_exp = self._img.metadata.get(model.MD_EXP_TIME, 0.1)  # s
        self.exposureTime = model.FloatContinuous(self._orig_exp, (1e-3, 10),
                                                  unit="s")

        # Some code care about the readout rate to know how long an acquisition will take
        self.readoutRate = model.FloatVA(1e9, unit="Hz", readonly=True)

        pxs = self._img.metadata.get(model.MD_PIXEL_SIZE, (10e-6, 10e-6))
        mag = self._img.metadata.get(model.MD_LENS_MAG, 1)
        spxs = tuple(s * mag for s in pxs)
        self.pixelSize = model.VigilantAttribute(spxs, unit="m", readonly=True)

        self._metadata = {
            model.MD_HW_NAME: "FakeCam",
            model.MD_SENSOR_PIXEL_SIZE: spxs,
            model.MD_DET_TYPE: model.MD_DT_INTEGRATING
        }

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

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

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

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

        # Include a thread which creates or fixes an hardware error in the simcam on the basis of the presence of the
        # file ERROR_STATE_FILE in model.BASE_DIRECTORY
        self._is_running = True
        self._error_creation_thread = threading.Thread(
            target=self._state_error_run, name="Creating and state error")
        self._error_creation_thread.daemon = True
        self._error_creation_thread.start()
Example #18
0
    def __init__(self, name, role, parent, image, spectrograph=None, daemon=None, **kwargs):
        """ Initializes a fake readout camera.
        :parameter name: (str) as in Odemis
        :parameter role: (str) as in Odemis
        :parameter parent: class streakcamera
        :parameter image: fake input image
        """
        # TODO image focus and operate mode
        # get the fake images
        try:
            image_filename = str(image)
            # ensure relative path is from this file
            if not os.path.isabs(image):
                image_filename = os.path.join(os.path.dirname(__file__), image)
            converter = dataio.find_fittest_converter(image_filename, mode=os.O_RDONLY)
            self._img_list = []
            img_list = converter.read_data(image_filename)
            for img in img_list:
                if img.ndim > 3:  # remove dims of length 1
                    img = numpy.squeeze(img)
                self._img_list.append(img)  # can be RGB or greyscale
        except Exception:
            raise ValueError("Fake image does not fit requirements for temporal spectrum acquisition.")

        super(ReadoutCamera, self).__init__(name, role, parent=parent,
                                            daemon=daemon, **kwargs)  # init HwComponent

        self.parent = parent

        self._metadata[model.MD_HW_VERSION] = 'Simulated readout camera OrcaFlash 4.0 V3, ' \
                                              'Product number: C13440-20C, Serial number: 301730'
        self._metadata[model.MD_SW_VERSION] = 'Firmware: 4.20.B, Version: 4.20.B03-A19-B02-4.02'
        self._metadata[model.MD_DET_TYPE] = model.MD_DT_INTEGRATING

        # sensor size (resolution)
        # x (lambda): horizontal, y (time): vertical
        full_res = (self._img_list[0].shape[1], self._img_list[0].shape[0])
        self._metadata[model.MD_SENSOR_SIZE] = full_res

        # 16-bit
        depth = 2 ** (self._img_list[0].dtype.itemsize * 8)
        self._shape = full_res + (depth,)

        # variable needed to update resolution VA and wavelength list correctly (_updateWavelengthList())
        self._binning = (2, 2)

        # need to be before binning, as it is modified when changing binning
        resolution = (int(full_res[0]/self._binning[0]), int(full_res[1]/self._binning[1]))
        self.resolution = model.ResolutionVA(resolution, ((1, 1), full_res), setter=self._setResolution)

        # variable needed to update wavelength list correctly (_updateWavelengthList())
        self._resolution = self.resolution.value

        choices_bin = {(1, 1), (2, 2), (4, 4)}
        self.binning = model.VAEnumerated(self._binning, choices_bin, setter=self._setBinning)
        self._metadata[model.MD_BINNING] = self.binning.value

        # physical pixel size is 6.5um x 6.5um
        sensor_pixelsize = (6.5e-06, 6.5e-06)
        self._metadata[model.MD_SENSOR_PIXEL_SIZE] = sensor_pixelsize

        # pixelsize VA is the sensor size, it does not include binning or magnification
        self.pixelSize = model.VigilantAttribute(sensor_pixelsize, unit="m", readonly=True)

        range_exp = [0.00001, 1]  # 10us to 1s
        self._exp_time = 0.1  # 100 msec
        self.exposureTime = model.FloatContinuous(self._exp_time, range_exp, unit="s", setter=self._setCamExpTime)
        self._metadata[model.MD_EXP_TIME] = self.exposureTime.value

        self.readoutRate = model.VigilantAttribute(425000000, unit="Hz", readonly=True)  # MHz
        self._metadata[model.MD_READOUT_TIME] = 1 / self.readoutRate.value  # s

        # spectrograph VAs after readout camera VAs
        self._spectrograph = spectrograph
        if self._spectrograph:
            logging.debug("Starting streak camera with spectrograph.")
            self._spectrograph.position.subscribe(self._updateWavelengthList, init=True)
        else:
            logging.warning("No spectrograph specified. No wavelength metadata will be attached.")

        # for synchronized acquisition
        self._sync_event = None
        self.softwareTrigger = model.Event()

        # Simple implementation of the flow: we keep generating images and if
        # there are subscribers, they'll receive it.
        self.data = SimpleStreakCameraDataFlow(self._start, self._stop, self._sync)
        self._generator = None

        self._img_counter = 0  # initialize the image counter
Example #19
0
    def __init__(self, name, role, children, **kwargs):
        '''
        children (dict string->model.HwComponent): the children
            There must be exactly two children "spectrograph" and "detector". The 
            first dimension of the CCD is supposed to be along the wavelength,
            with the first pixels representing the lowest wavelengths. 
        Raise an ValueError exception if the children are not compatible
        '''
        # we will fill the set of children with Components later in ._children
        model.Detector.__init__(self, name, role, **kwargs)

        # Check the children
        dt = children["detector"]
        if not isinstance(dt, ComponentBase):
            raise ValueError("Child detector is not a component.")
        if not hasattr(dt, "shape") or not isinstance(dt.shape, tuple):
            raise ValueError("Child detector is not a Detector component.")
        if not hasattr(dt, "data") or not isinstance(dt.data, DataFlowBase):
            raise ValueError("Child detector is not a Detector component.")
        self._detector = dt
        self.children.add(dt)

        sp = children["spectrograph"]
        if not isinstance(sp, ComponentBase):
            raise ValueError("Child spectrograph is not a component.")
        try:
            if not "wavelength" in sp.axes:
                raise ValueError(
                    "Child spectrograph has no 'wavelength' axis.")
        except Exception:
            raise ValueError("Child spectrograph is not an Actuator.")
        self._spectrograph = sp
        self.children.add(sp)

        # set up the detector part
        # check that the shape is "horizontal"
        if dt.shape[0] <= 1:
            raise ValueError(
                "Child detector must have at least 2 pixels horizontally")
        if dt.shape[0] < dt.shape[1]:
            logging.warning(
                "Child detector is shaped vertically (%dx%d), "
                "this is probably incorrect, as wavelengths are "
                "expected to be along the horizontal axis", dt.shape[0],
                dt.shape[1])
        # shape is same as detector (raw sensor), but the max resolution is always flat
        self._shape = tuple(dt.shape)  # duplicate

        # The resolution and binning are derived from the detector, but with
        # settings set so that there is only one horizontal line.

        # TODO: give a init parameter or VA to specify a smaller window height
        # than the entire CCD (some spectrometers have only noise on the top and
        # bottom)
        if dt.binning.range[1][1] < dt.resolution.range[1][1]:
            # without software binning, we are stuck to the max binning
            logging.info(
                "Spectrometer %s will only use a %d px band of the %d "
                "px of the sensor", name, dt.binning.range[1][1],
                dt.resolution.range[1][1])

        resolution = [dt.resolution.range[1][0], 1]  # max,1
        binning = [1, 1]
        # horizontally: as fine as possible, with a maximum around 256px, over
        #  this, use binning if possible
        binning[0] = min(max(resolution[0] // 256, dt.binning.range[0][0]),
                         dt.binning.range[1][0])
        resolution[0] //= binning[0]

        # vertically: 1, with binning as big as possible
        binning[1] = min(dt.binning.range[1][1], dt.resolution.range[1][1])

        min_res = (dt.resolution.range[0][0], 1)
        max_res = (dt.resolution.range[1][0], 1)
        self.resolution = model.ResolutionVA(resolution, [min_res, max_res],
                                             setter=self._setResolution)
        # 2D binning is like a "small resolution"
        self._binning = binning
        self.binning = model.ResolutionVA(self._binning,
                                          dt.binning.range,
                                          setter=self._setBinning)

        self._setBinning(binning)  # will also update the resolution

        # TODO: support software binning by rolling up our own dataflow that
        # does data merging
        assert dt.resolution.range[0][1] == 1
        self.data = dt.data

        # duplicate every other VA and Event from the detector
        # that includes required VAs like .pixelSize and .exposureTime
        for aname, value in model.getVAs(dt).items() + model.getEvents(
                dt).items():
            if not hasattr(self, aname):
                setattr(self, aname, value)
            else:
                logging.debug(
                    "skipping duplication of already existing VA '%s'", aname)

        assert hasattr(self, "pixelSize")
        assert hasattr(self, "exposureTime")

        # Update metadata of detector with wavelength conversion
        # whenever the wavelength/grating axes moves.
        try:
            self._pn_phys = sp.getPolyToWavelength()
        except AttributeError:
            raise ValueError(
                "Child spectrograph has no getPolyToWavelength() method")

        sp.position.subscribe(self._onPositionUpdate)
        self.resolution.subscribe(self._onResBinningUpdate)
        self.binning.subscribe(self._onResBinningUpdate, init=True)
Example #20
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'})
Example #21
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)
Example #22
0
    def __init__(self, name, role, sn=None, **kwargs):
        """
        sn (string or None): serial number of the device to open.
            If None, it will pick the first device found.
            If "fake", it will use a simulated device.
        """
        super(Spectrometer, self).__init__(name, role, **kwargs)
        if sn == "fake":
            self._dll = FakeAvantesDLL()
            sn = None
        else:
            self._dll = AvantesDLL()

        # Look for the spectrometer and initialize it
        self._dev_id, self._dev_hdl = self._open_device(sn)
        fpga_ver, fw_ver, lib_ver = self.GetVersionInfo()
        config = self.GetParameter()
        sensor = config.Detector.SensorType
        sensor_name = SensorTypes.get(sensor, str(sensor))
        self._swVersion = "libavs v%s" % (lib_ver,)
        # Reported UserFriendlyName is the same as SerialNumber
        self._hwVersion = ("AvaSpec sensor %s (s/n %s) FPGA v%s, FW v%s " %
                           (sensor_name, self._dev_id.SerialNumber.decode("ascii"), fpga_ver, fw_ver))
        self._metadata[model.MD_HW_VERSION] = self._hwVersion
        self._metadata[model.MD_SW_VERSION] = self._swVersion

        # Note: It seems that by default it uses the maximum cooling temperature.
        # so that's good enough for us. We could try to change it with config.TecControl.Setpoint.

        npixels = self.GetNumPixels()
        # TODO: are there drawbacks in using it in 16-bits? The demo always set it
        # to 16-bits. Is that just for compatibility with old hardware?
        self.UseHighResAdc(True)  # Default is 14 bits
        # Intensity is in float, but range is based on uint16
        self._shape = (npixels, 1, float(2 ** 16))

        # The hardware light diffraction is fixed, and there is no support for
        # binning, and we don't accept cropping, so the wavelength is completely fixed.
        self._metadata[model.MD_WL_LIST] = list(self.GetLambda(npixels) * 1e-9)
        # Indicate the data contains spectrum on the "fast" dimension
        self._metadata[model.MD_DIMS] = "XC"

        self.exposureTime = model.FloatContinuous(1, INTEGRATION_TIME_RNG, unit="s",
                                                  setter=self._onExposureTime)

        # Not so useful, but makes happy some client when trying to estimate the
        # acquisition time. Not sure whether this is correct, but it's good enough
        self.readoutRate = model.VigilantAttribute(CLOCK_RATE, readonly=True, unit="Hz")

        # No support for binning/resolution change, but we put them, as it helps
        # to follow the standard interface, so there rest of Odemis is happy
        self.binning = model.ResolutionVA((1, 1), ((1, 1), (1, 1)))
        self.resolution = model.ResolutionVA((npixels, 1), ((npixels, 1), (npixels, 1)))

        self.data = AvantesDataFlow(self)
        self.softwareTrigger = model.Event()

        # Queue to control the acquisition thread
        self._genmsg = queue.Queue()  # GEN_*
        # Queue of all synchronization events received (typically max len 1)
        self._old_triggers = []
        self._data_ready = threading.Event()  # set when new data is available
        # Thread of the generator
        self._generator = None
Example #23
0
 def __init__(self, name, role, array, **kwargs):
     super(StaticCCD, self).__init__(name, role, **kwargs)
     self.array = array
     self.data = StaticImageDataFlow(self)
     res = self.array.shape
     self.resolution = model.ResolutionVA(res, (res, res), readonly=True)
Example #24
0
    def __init__(self,
                 name,
                 role,
                 image,
                 dependencies=None,
                 daemon=None,
                 blur_factor=1e4,
                 resolution=None,
                 **kwargs):
        '''
        dependencies (dict string->Component): If "focus" is passed, and it's an
            actuator with a z axis, the image will be blurred based on the
            position, to simulate a focus axis.
        image (str or None): path to a file to use as fake image (relative to
         the directory of this class)
        '''

        # TODO: support transpose? If not, warn that it's not accepted
        # fake image setup
        image = unicode(image)
        # ensure relative path is from this file
        if not os.path.isabs(image):
            image = os.path.join(os.path.dirname(__file__), image)
        converter = dataio.find_fittest_converter(image, mode=os.O_RDONLY)
        self._imgs = converter.read_data(image)  # can be RGB or greyscale
        model.DigitalCamera.__init__(self,
                                     name,
                                     role,
                                     dependencies=dependencies,
                                     daemon=daemon,
                                     **kwargs)

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

        for img in self._imgs[1:]:
            if self._imgs[0].shape != self._imgs[i].shape:
                raise ValueError("all images must have the same resolution")
        imshp = self._imgs[0].shape

        # For RGB, the colour is last dim, but we still indicate it as higher
        # dimension to ensure shape always starts with X, Y
        if len(imshp) == 3 and imshp[-1] in {3, 4}:
            # resolution doesn't affect RGB dim
            if resolution:
                if resolution >= imshp[-2::-1]:
                    res = tuple(resolution)
            else:
                res = imshp[-2::-1]
            self._shape = res + imshp[-1::]  # X, Y, C
        else:
            if resolution:
                res = tuple(resolution)
            else:
                res = imshp[::-1]

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

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

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

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

        exp = self._imgs[0].metadata.get(model.MD_EXP_TIME, 0.1)  # s
        self.exposureTime = model.FloatContinuous(exp, (1e-3, 1e3), unit="s")
        # Some code care about the readout rate to know how long an acquisition will take
        self.readoutRate = model.FloatVA(1e9, unit="Hz", readonly=True)

        pxs = self._imgs[0].metadata.get(model.MD_PIXEL_SIZE, (10e-6, 10e-6))
        mag = self._imgs[0].metadata.get(model.MD_LENS_MAG, 1)
        spxs = tuple(s * mag for s in pxs)
        self.pixelSize = model.VigilantAttribute(spxs, unit="m", readonly=True)

        self._metadata = {
            model.MD_HW_NAME: "FakeCam",
            model.MD_SENSOR_PIXEL_SIZE: spxs,
            model.MD_DET_TYPE: model.MD_DT_INTEGRATING,
            model.MD_PIXEL_SIZE: pxs
        }

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

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

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

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

        # Simple implementation of the flow: we keep generating images and if
        # there are subscribers, they'll receive it.
        self.data = SimpleDataFlow(self)
        self._generator = None
        # Convenience event for the user to connect and fire
        self.softwareTrigger = model.Event()
Example #25
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)
Example #26
0
    def __init__(self, name, role, children, **kwargs):
        '''
        children (dict string->model.HwComponent): the children
            There must be exactly two children "spectrograph" and "detector". The
            first dimension of the CCD is supposed to be along the wavelength,
            with the first pixels representing the lowest wavelengths.
        Raise:
          ValueError: if the children are not compatible
        '''
        # we will fill the set of children with Components later in ._children
        model.Detector.__init__(self, name, role, **kwargs)

        # Check the children
        dt = children["detector"]
        if not isinstance(dt, ComponentBase):
            raise ValueError("Child detector is not a component.")
        if ((not hasattr(dt, "shape") or not isinstance(dt.shape, tuple)) or
            not model.hasVA(dt, "pixelSize")):
            raise ValueError("Child detector is not a Detector component.")
        if not hasattr(dt, "data") or not isinstance(dt.data, DataFlowBase):
            raise ValueError("Child detector has not .data DataFlow.")
        self._detector = dt
        self.children.value.add(dt)

        sp = children["spectrograph"]
        if not isinstance(sp, ComponentBase):
            raise ValueError("Child spectrograph is not a component.")
        try:
            if "wavelength" not in sp.axes:
                raise ValueError("Child spectrograph has no 'wavelength' axis.")
        except Exception:
            raise ValueError("Child spectrograph is not an Actuator.")
        self._spectrograph = sp
        self.children.value.add(sp)

        # set up the detector part
        # check that the shape is "horizontal"
        if dt.shape[0] <= 1:
            raise ValueError("Child detector must have at least 2 pixels horizontally")
        if dt.shape[0] < dt.shape[1]:
            logging.warning("Child detector is shaped vertically (%dx%d), "
                            "this is probably incorrect, as wavelengths are "
                            "expected to be along the horizontal axis",
                            dt.shape[0], dt.shape[1])
        # shape is same as detector (raw sensor), but the max resolution is always flat
        self._shape = tuple(dt.shape) # duplicate

        # Wrapper for the dataflow
        self.data = SpecDataFlow(self, dt.data)

        # The resolution and binning are derived from the detector, but with
        # settings set so that there is only one horizontal line.

        # TODO: give a init parameter or VA to specify a smaller window height
        # than the entire CCD (some spectrometers have only noise on the top and
        # bottom)
        if dt.binning.range[1][1] < dt.resolution.range[1][1]:
            # without software binning, we are stuck to the max binning
            # TODO: support software binning by rolling up our own dataflow that
            # does data merging
            logging.info("Spectrometer %s will only use a %d px band of the %d "
                         "px of the sensor", name, dt.binning.range[1][1],
                         dt.resolution.range[1][1])

        assert dt.resolution.range[0][1] == 1
        resolution = (dt.resolution.range[1][0], 1)  # max,1
        min_res = (dt.resolution.range[0][0], 1)
        max_res = (dt.resolution.range[1][0], 1)
        self.resolution = model.ResolutionVA(resolution, (min_res, max_res),
                                             setter=self._setResolution)
        # 2D binning is like a "small resolution"
        # Initial binning is minimum binning horizontally, and maximum vertically
        self._binning = (1, min(dt.binning.range[1][1], dt.resolution.range[1][1]))
        self.binning = model.ResolutionVA(self._binning, dt.binning.range,
                                          setter=self._setBinning)

        self._setBinning(self._binning) # will also update the resolution

        # TODO: also wrap translation, if it exists?

        # duplicate every other VA and Event from the detector
        # that includes required VAs like .pixelSize and .exposureTime
        for aname, value in model.getVAs(dt).items() + model.getEvents(dt).items():
            if not hasattr(self, aname):
                setattr(self, aname, value)
            else:
                logging.debug("skipping duplication of already existing VA '%s'", aname)

        assert hasattr(self, "pixelSize")
        if not model.hasVA(self, "exposureTime"):
            logging.warning("Spectrometer %s has no exposureTime VA", name)

        sp.position.subscribe(self._onPositionUpdate)
        self.resolution.subscribe(self._onResBinning)
        self.binning.subscribe(self._onResBinning)
        self._updateWavelengthList()
Example #27
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)
Example #28
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()
Example #29
0
    def __init__(self, name, role, image, children=None, daemon=None, **kwargs):
        '''
        children (dict string->kwargs): parameters setting for the children.
            The only possible child is "focus".
            They will be provided back in the .children VA
        image (str or None): path to a file to use as fake image (relative to
         the directory of this class)
        '''
        # TODO: support transpose? If not, warn that it's not accepted
        # fake image setup
        image = unicode(image)
        # ensure relative path is from this file
        if not os.path.isabs(image):
            image = os.path.join(os.path.dirname(__file__), image)
        converter = dataio.find_fittest_converter(image, mode=os.O_RDONLY)
        self._img = converter.read_data(image)[0]  # can be RGB or greyscale

        # we will fill the set of children with Components later in ._children
        model.DigitalCamera.__init__(self, name, role, daemon=daemon, **kwargs)

        if self._img.ndim > 3:  # remove dims of length 1
            self._img = numpy.squeeze(self._img)

        imshp = self._img.shape
        if len(imshp) == 3 and imshp[0] in {3, 4}:
            # CYX, change it to YXC, to simulate a RGB detector
            self._img = numpy.rollaxis(self._img, 2) # XCY
            self._img = numpy.rollaxis(self._img, 2) # YXC
            imshp = self._img.shape

        # For RGB, the colour is last dim, but we still indicate it as higher
        # dimension to ensure shape always starts with X, Y
        if len(imshp) == 3 and imshp[-1] in {3, 4}:
            # resolution doesn't affect RGB dim
            res = imshp[-2::-1]
            self._shape = res + imshp[-1::] # X, Y, C
            # indicate it's RGB pixel-per-pixel ordered
            self._img.metadata[model.MD_DIMS] = "YXC"
        else:
            res = imshp[::-1]
            self._shape = res # X, Y,...
        # TODO: handle non integer dtypes
        depth = 2 ** (self._img.dtype.itemsize * 8)
        self._shape += (depth,)

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

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

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

        exp = self._img.metadata.get(model.MD_EXP_TIME, 0.1) # s
        self.exposureTime = model.FloatContinuous(exp, (1e-3, 1e3), unit="s")
        # Some code care about the readout rate to know how long an acquisition will take
        self.readoutRate = model.FloatVA(1e9, unit="Hz", readonly=True)

        pxs = self._img.metadata.get(model.MD_PIXEL_SIZE, (10e-6, 10e-6))
        mag = self._img.metadata.get(model.MD_LENS_MAG, 1)
        spxs = tuple(s * mag for s in pxs)
        self.pixelSize = model.VigilantAttribute(spxs, unit="m", readonly=True)

        self._metadata = {model.MD_HW_NAME: "FakeCam",
                          model.MD_SENSOR_PIXEL_SIZE: spxs,
                          model.MD_DET_TYPE: model.MD_DT_INTEGRATING}

        try:
            kwargs = children["focus"]
        except (KeyError, TypeError):
            logging.info("Will not simulate focus")
            self._focus = None
        else:
            self._focus = CamFocus(parent=self, daemon=daemon, **kwargs)
            self.children.value = self.children.value | {self._focus}

        # Simple implementation of the flow: we keep generating images and if
        # there are subscribers, they'll receive it.
        self.data = SimpleDataFlow(self)
        self._generator = None
        # Convenience event for the user to connect and fire
        self.softwareTrigger = model.Event()
Example #30
0
    def __init__(self,
                 name,
                 role,
                 image,
                 children=None,
                 daemon=None,
                 **kwargs):
        '''
        children (dict string->kwargs): parameters setting for the children.
            The only possible child is "focus".
            They will be provided back in the .children VA
        image (str or None): path to a file to use as fake image (relative to
         the directory of this class)
        '''
        # TODO: support transpose? If not, warn that it's not accepted
        # fake image setup
        image = unicode(image)
        # change to this directory to ensure relative path is from this file
        os.chdir(os.path.dirname(unicode(__file__)))
        exporter = dataio.find_fittest_exporter(image)
        self._img = exporter.read_data(image)[0]  # can be RGB or greyscale

        # we will fill the set of children with Components later in ._children
        model.DigitalCamera.__init__(self, name, role, daemon=daemon, **kwargs)

        if self._img.ndim > 3:  # remove dims of length 1
            self._img = numpy.squeeze(self._img)

        imshp = self._img.shape
        if len(imshp) == 3 and imshp[0] in {3, 4}:
            # CYX, change it to YXC, to simulate a RGB detector
            self._img = numpy.rollaxis(self._img, 2)  # XCY
            self._img = numpy.rollaxis(self._img, 2)  # YXC
            imshp = self._img.shape

        # For RGB, the colour is last dim, but we still indicate it as higher
        # dimension to ensure shape always starts with X, Y
        if len(imshp) == 3 and imshp[-1] in {3, 4}:
            # resolution doesn't affect RGB dim
            res = imshp[-2::-1]
            self._shape = res + imshp[-1::]  # X, Y, C
            # indicate it's RGB pixel-per-pixel ordered
            self._img.metadata[model.MD_DIMS] = "YXC"
        else:
            res = imshp[::-1]
            self._shape = res  # X, Y,...
        # TODO: handle non integer dtypes
        depth = 2**(self._img.dtype.itemsize * 8)
        self._shape += (depth, )

        # TODO: don't provide range? or don't make it readonly?
        self.resolution = model.ResolutionVA(res,
                                             [res, res])  # , readonly=True)
        # TODO: support (simulated) binning
        self.binning = model.ResolutionVA((1, 1), [(1, 1), (1, 1)])

        exp = self._img.metadata.get(model.MD_EXP_TIME, 0.1)  # s
        self.exposureTime = model.FloatContinuous(exp, [1e-3, 1e3], unit="s")
        # Some code care about the readout rate to know how long an acquisition will take
        self.readoutRate = model.FloatVA(1e9, unit="Hz", readonly=True)

        pxs = self._img.metadata.get(model.MD_PIXEL_SIZE, (10e-6, 10e-6))
        mag = self._img.metadata.get(model.MD_LENS_MAG, 1)
        spxs = tuple(s * mag for s in pxs)
        self.pixelSize = model.VigilantAttribute(spxs, unit="m", readonly=True)

        self._metadata = {
            model.MD_HW_NAME: "FakeCam",
            model.MD_SENSOR_PIXEL_SIZE: spxs
        }

        try:
            kwargs = children["focus"]
        except (KeyError, TypeError):
            logging.info("Will not simulate focus")
            self._focus = None
        else:
            self._focus = CamFocus(parent=self, daemon=daemon, **kwargs)
            self.children.value = self.children.value | {self._focus}

        # Simple implementation of the flow: we keep generating images and if
        # there are subscribers, they'll receive it.
        self.data = model.DataFlow(self)
        self._generator = util.RepeatingTimer(exp, self._generate,
                                              "SimCam image generator")
        self._generator.start()