Exemple #1
0
    def __init__(self, main):
        assert main.microscope is not None
        MicroscopyGUIData.__init__(self, main)

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

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

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

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

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

        # VA for autofocus procedure mode
        self.autofocus_active = BooleanVA(False)
Exemple #2
0
    def __init__(self, main):
        self.main = main

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

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

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

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

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

        # The subset of views taken from `views` that *can* actually displayed,
        # but they might be hidden as well.
        # This attribute is also handled and manipulated by the ViewController.
        self.visible_views = model.ListVA()
Exemple #3
0
    def duplicate_tab_data_model(self, orig):
        """
        Duplicate a MicroscopyGUIData and adapt it for the acquisition window
        The streams will be shared, but not the views
        orig (MicroscopyGUIData)
        return (MicroscopyGUIData)
        """
        # TODO: we'd better create a new view and copy the streams
        new = copy.copy(orig)  # shallow copy

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

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

        # differentiate it (only one view)
        new.views = model.ListVA()
        new.views.value.append(view)
        new.focussedView = model.VigilantAttribute(view)
        new.viewLayout = model.IntEnumerated(guimodel.VIEW_LAYOUT_ONE,
                                             choices={guimodel.VIEW_LAYOUT_ONE})
        new.tool = model.IntEnumerated(TOOL_NONE, choices={TOOL_NONE})
        return new
Exemple #4
0
    def __init__(self,
                 name,
                 role,
                 host,
                 port=DEFAULT_PORT,
                 channel=1,
                 limits=(0, 10),
                 **kwargs):
        '''
        host (str): Host name or IP address of the device. Use "fake" for using
        a simulator.
        port (int): The TCP/IP port of the device. Set to a default value of 5555
        channel (int, 1 or 2): The output channel to use on the device.
        limits (tuple of 2 floats): min/max in V
        '''
        super(WaveGenerator, self).__init__(name, role, **kwargs)

        self._host = host
        self._port = port
        self._channel = channel
        self._accesser = self._openConnection(self._host, self._port)
        self._recover = False

        # Set the default specified period
        self._hwVersion = self._sendQueryCommand("*IDN?")

        # Internal settings
        lo, hi = limits
        self._amplitude_pp = hi - lo
        if self._amplitude_pp < 0:
            raise ValueError("Invalid negative amplitude specified.")
        self._phase_shift = 0
        self._dc_bias = self._amplitude_pp / 2 + lo

        self._duty_cycle = 50  #%
        self.ApplyDutyCycle(self._duty_cycle)
        self._checkForError()

        # Read frequency from the device.
        frequency = self.GetFrequency()
        self.period = model.FloatContinuous(1 / frequency,
                                            range=PERIOD_RNG,
                                            unit="s",
                                            setter=self._setPeriod)
        self.power = model.IntEnumerated(0, {0, 1},
                                         unit="",
                                         setter=self._setPower)
        # make sure it is off.
        self._setPower(self.power.value)
Exemple #5
0
    def __init__(self, name, role, parent, **kwargs):
        """
        Note: parent should have a child "scanner" already initialised
        """
        # It will set up ._shape and .parent
        model.Detector.__init__(self, name, role, parent=parent, **kwargs)
        self.data = SEMDataFlow(self, parent)
        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.parent.fake_img
        # The shape is just one point, the depth
        idt = numpy.iinfo(self.fake_img.dtype)
        data_depth = idt.max - idt.min + 1
        self._shape = (data_depth,) # only one point

        # 8 or 16 bits image
        if data_depth == 255:
            bpp = 8
        else:
            bpp = 16
        self.bpp = model.IntEnumerated(bpp, {8, 16})

        # Simulate the Hw brightness/contrast, but don't actually do anything
        self.contrast = model.FloatContinuous(0.5, [0, 1], unit="")
        self.brightness = model.FloatContinuous(0.5, [0, 1], unit="")

        self.drift_factor = 2  # dummy value for drift in pixels
        self.current_drift = 0
        # Given that max resolution is half the shape of fake_img,
        # we set the drift bound to stay inside the fake_img bounds
        self.drift_bound = min(v // 4 for v in self.fake_img.shape[::-1])
        self._update_drift_timer = util.RepeatingTimer(parent._drift_period,
                                                       self._update_drift,
                                                       "Drift update")
        if parent._drift_period:
            self._update_drift_timer.start()

        # Special event to request software unblocking on the scan
        self.softwareTrigger = model.Event()

        self._metadata[model.MD_DET_TYPE] = model.MD_DT_NORMAL
Exemple #6
0
    def duplicate_tab_data_model(self, orig):
        """
        Duplicate a MicroscopyGUIData and adapt it for the acquisition window
        The streams will be shared, but not the views
        orig (MicroscopyGUIData)
        return (MicroscopyGUIData)
        """
        new = copy.copy(orig) # shallow copy

        # create view (which cannot move or focus)
        view = guimodel.MicroscopeView(orig.focussedView.value.name.value)

        # differentiate it (only one view)
        new.views = {"all": view}
        new.focussedView = model.VigilantAttribute(view)
        new.viewLayout = model.IntEnumerated(guimodel.VIEW_LAYOUT_ONE,
                                             choices=set([guimodel.VIEW_LAYOUT_ONE]))

        return new
Exemple #7
0
    def __init__(self, microscope, main_app):
        super(ARspectral, self).__init__(microscope, main_app)

        # Can only be used on a Sparc with a CCD
        if not microscope:
            return

        main_data = self.main_app.main_data
        self.ebeam = main_data.ebeam
        self.ccd = main_data.ccd
        self.sed = main_data.sed
        self.sgrh = main_data.spectrograph
        if not all((self.ebeam, self.ccd, self.sed, self.sgrh)):
            logging.debug("Hardware not found, cannot use the plugin")
            return

        # TODO: handle SPARC systems which don't have such hardware
        bigslit = model.getComponent(role="slit-in-big")
        lsw = model.getComponent(role="lens-switch")

        # This is a little tricky: we don't directly need the spectrometer, the
        # 1D image of the CCD, as we are interested in the raw image. However,
        # we care about the wavelengths and the spectrometer might be inverted
        # in order to make sure the wavelength is is the correct direction (ie,
        # lowest pixel = lowest wavelength). So we need to do the same on the
        # raw image. However, there is no "official" way to connect the
        # spectrometer(s) to their raw CCD. So we rely on the fact that
        # typically this is a wrapper, so we can check using the .dependencies.
        wl_inverted = False
        try:
            spec = self._find_spectrometer(self.ccd)
        except LookupError as ex:
            logging.warning("%s, expect that the wavelengths are not inverted",
                            ex)
        else:
            # Found spec => check transpose in X (1 or -1), and invert if it's inverted (-1)
            try:
                wl_inverted = (spec.transpose[0] == -1)
            except Exception as ex:
                # Just in case spec has no .transpose or it's not a tuple
                # (very unlikely as all Detectors have it)
                logging.warning(
                    "%s: expect that the wavelengths are not inverted", ex)

        # the SEM survey stream (will be updated when showing the window)
        self._survey_s = None

        # Create a stream for AR spectral measurement
        self._ARspectral_s = SpectralARScanStream("AR Spectrum", self.ccd,
                                                  self.sed, self.ebeam,
                                                  self.sgrh, lsw, bigslit,
                                                  main_data.opm, wl_inverted)

        # For reading the ROA and anchor ROI
        self._tab = main_data.getTabByName("sparc_acqui")
        self._tab_data = self._tab.tab_data_model

        # The settings to be displayed in the dialog
        # Trick: we use the same VAs as the stream, so they are directly synchronised
        self.centerWavelength = self._ARspectral_s.centerWavelength
        #self.numberOfPixels = self._ARspectral_s.numberOfPixels
        self.dwellTime = self._ARspectral_s.dwellTime
        self.slitWidth = self._ARspectral_s.slitWidth
        self.binninghorz = self._ARspectral_s.binninghorz
        self.binningvert = self._ARspectral_s.binningvert
        self.nDC = self._ARspectral_s.nDC
        self.grating = model.IntEnumerated(
            self.sgrh.position.value["grating"],
            choices=self.sgrh.axes["grating"].choices,
            setter=self._onGrating)
        self.roi = self._ARspectral_s.roi
        self.stepsize = self._ARspectral_s.stepsize
        self.res = model.TupleVA((1, 1), unit="px")
        self.cam_res = model.TupleVA((self.ccd.shape[0], self.ccd.shape[1]),
                                     unit="px")
        self.gain = self.ccd.gain
        self.readoutRate = self.ccd.readoutRate
        self.filename = model.StringVA("a.h5")
        self.expectedDuration = model.VigilantAttribute(1,
                                                        unit="s",
                                                        readonly=True)

        # Update the expected duration when values change, depends both dwell time and # of pixels
        self.dwellTime.subscribe(self._update_exp_dur)
        self.stepsize.subscribe(self._update_exp_dur)
        self.nDC.subscribe(self._update_exp_dur)
        self.readoutRate.subscribe(self._update_exp_dur)
        self.cam_res.subscribe(self._update_exp_dur)

        # subscribe to update X/Y res
        self.stepsize.subscribe(self._update_res)
        self.roi.subscribe(self._update_res)
        #subscribe to binning values for camera res
        self.binninghorz.subscribe(self._update_cam_res)
        self.binningvert.subscribe(self._update_cam_res)

        self.addMenu("Acquisition/AR Spectral...", self.start)
Exemple #8
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()
Exemple #9
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)
Exemple #10
0
    def __init__(self, microscope, main_app):
        super(ARspectral, self).__init__(microscope, main_app)

        # Can only be used on a Sparc with a CCD
        if not microscope:
            return

        main_data = self.main_app.main_data
        self.ebeam = main_data.ebeam
        self.ccd = main_data.ccd
        self.sed = main_data.sed
        self.sgrh = main_data.spectrograph
        if not all((self.ebeam, self.ccd, self.sed, self.sgrh)):
            logging.debug("Hardware not found, cannot use the plugin")
            return

        # TODO: handle SPARC systems which don't have such hardware
        bigslit = model.getComponent(role="slit-in-big")
        lsw = model.getComponent(role="lens-switch")

        # the SEM survey stream (will be updated when showing the window)
        self._survey_s = None

        # Create a stream for AR spectral measurement
        self._ARspectral_s = SpectralARScanStream("AR Spectrum", self.ccd,
                                                  self.sed, self.ebeam,
                                                  self.sgrh, lsw, bigslit,
                                                  main_data.opm)

        # For reading the ROA and anchor ROI
        self._acqui_tab = main_app.main_data.getTabByName(
            "sparc_acqui").tab_data_model

        # The settings to be displayed in the dialog
        # Trick: we use the same VAs as the stream, so they are directly synchronised
        self.centerWavelength = self._ARspectral_s.centerWavelength
        #self.numberOfPixels = self._ARspectral_s.numberOfPixels
        self.dwellTime = self._ARspectral_s.dwellTime
        self.slitWidth = self._ARspectral_s.slitWidth
        self.binninghorz = self._ARspectral_s.binninghorz
        self.binningvert = self._ARspectral_s.binningvert
        self.nDC = self._ARspectral_s.nDC
        self.grating = model.IntEnumerated(
            self.sgrh.position.value["grating"],
            choices=self.sgrh.axes["grating"].choices,
            setter=self._onGrating)
        self.roi = self._ARspectral_s.roi
        self.stepsize = self._ARspectral_s.stepsize
        self.res = model.TupleVA((1, 1), unit="px")
        self.cam_res = model.TupleVA((self.ccd.shape[0], self.ccd.shape[1]),
                                     unit="px")
        self.gain = self.ccd.gain
        self.readoutRate = self.ccd.readoutRate
        self.filename = model.StringVA("a.h5")
        self.expectedDuration = model.VigilantAttribute(1,
                                                        unit="s",
                                                        readonly=True)

        # Update the expected duration when values change, depends both dwell time and # of pixels
        self.dwellTime.subscribe(self._update_exp_dur)
        self.stepsize.subscribe(self._update_exp_dur)
        self.nDC.subscribe(self._update_exp_dur)

        # subscribe to update X/Y res
        self.stepsize.subscribe(self._update_res)
        self.roi.subscribe(self._update_res)
        #subscribe to binning values for camera res
        self.binninghorz.subscribe(self._update_cam_res)
        self.binningvert.subscribe(self._update_cam_res)

        self.addMenu("Acquisition/AR Spectral...", self.start)
Exemple #11
0
    def __init__(self, microscope):
        """
        microscope (model.Microscope or None): the root of the HwComponent tree
         provided by the back-end. If None, it means the interface is not
         connected to a microscope (and displays a recorded acquisition).
        """

        self.microscope = microscope
        self.role = None

        # These are either HwComponents or None (if not available)
        self.ccd = None
        self.stage = None
        self.focus = None  # actuator to change the camera focus
        self.aligner = None  # actuator to align ebeam/ccd
        self.mirror = None  # actuator to change the mirror position (on SPARC)
        self.light = None
        self.light_filter = None  # emission light filter for SECOM/output filter for SPARC
        self.lens = None
        self.ebeam = None
        self.ebeam_focus = None  # change the e-beam focus
        self.sed = None  # secondary electron detector
        self.bsd = None  # back-scatter electron detector
        self.spectrometer = None  # spectrometer
        self.spectrograph = None  # actuator to change the wavelength
        self.ar_spec_sel = None  # actuator to select AR/Spectrometer (SPARC)
        self.lens_switch = None  # actuator to (de)activate the lens (SPARC)
        self.chamber = None  # actuator to control the chamber (pressure)
        self.ccd_chamber = None  # view of inside the chamber
        self.ccd_overview = None  # global view from above the sample

        # Indicates whether the microscope is acquiring a high quality image
        self.is_acquiring = model.BooleanVA(False)

        if microscope:
            self.role = microscope.role

            for d in microscope.detectors:
                if d.role == "ccd":
                    self.ccd = d
                elif d.role == "se-detector":
                    self.sed = d
                elif d.role == "bs-detector":
                    self.bsd = d
                elif d.role == "spectrometer":
                    self.spectrometer = d
                elif d.role == "ccd-chamber":
                    self.ccd_chamber = d
                elif d.role == "ccd-overview":
                    self.ccd_overview = d

            for a in microscope.actuators:
                if a.role == "stage":
                    self.stage = a  # most views move this actuator when moving
                elif a.role == "focus":
                    self.focus = a
                elif a.role == "ebeam-focus":
                    self.ebeam_focus = a
                elif a.role == "mirror":
                    self.mirror = a
                elif a.role == "align":
                    self.aligner = a
                elif a.role == "lens-switch":
                    self.lens_switch = a
                elif a.role == "ar-spec-selector":
                    self.ar_spec_sel = a
                elif a.role == "chamber":
                    self.chamber = a

            # Spectrograph is not directly an actuator, but a sub-comp of spectrometer
            if self.spectrometer:
                for child in self.spectrometer.children:
                    if child.role == "spectrograph":
                        self.spectrograph = child

            for e in microscope.emitters:
                if e.role == "light":
                    self.light = e
                    self._light_power_on = None  # None = unknown
                elif e.role == "filter":
                    self.light_filter = e
                elif e.role == "lens":
                    self.lens = e
                elif e.role == "e-beam":
                    self.ebeam = e

            # Do some typical checks on expectations from an actual microscope
            if not any((self.ccd, self.sed, self.bsd, self.spectrometer)):
                raise KeyError("No detector found in the microscope")

            if not self.light and not self.ebeam:
                raise KeyError("No emitter found in the microscope")

        # TODO: all that on/off thing is crazy:
        # * we cannot do it (for now)
        # * we'd better turn on/off the hardware when streams need it
        # * pause and off are the same things but for SEM (blank/off)
        # * optical on in live view means light on, while in lens align it means light off
        # => we'd be better with just one global pause button (and pressure)

        # Handle turning on/off the instruments
        hw_states = set([STATE_OFF, STATE_ON, STATE_PAUSE])
        if self.ccd:
            # not so nice to hard code it here, but that should do it for now...
            if self.role == "sparc":
                self.arState = model.IntEnumerated(STATE_OFF,
                                                   choices=hw_states)
                self.arState.subscribe(self.onARState)
            else:
                self.opticalState = model.IntEnumerated(STATE_OFF,
                                                        choices=hw_states)
                self.opticalState.subscribe(self.onOpticalState)

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

        if self.spectrometer:
            self.specState = model.IntEnumerated(STATE_OFF, choices=hw_states)
            self.specState.subscribe(self.onSpecState)

        # Used when doing fine alignment, based on the value used by the user
        # when doing manual alignment. 0.1s is not too bad value if the user
        # hasn't specified anything (yet).
        self.fineAlignDwellTime = model.FloatContinuous(0.1,
                                                        range=[1e-9, 100],
                                                        unit="s")

        # TODO: should we put also the configuration related stuff?
        # Like path/file format
        # Set to True to request debug info to be displayed
        self.debug = model.BooleanVA(False)

        # Current tab (+ all available tabs in choices as a dict tab -> name)
        # Fully set and managed later by the TabBarController.
        # Not very beautiful because Tab is not part of the model.
        # MicroscopyGUIData would be better in theory, but is less convenient
        # do directly access additional GUI information.
        self.tab = model.VAEnumerated(None, choices={None: ""})
Exemple #12
0
    def __init__(self, microscope):
        """
        :param microscope: (model.Microscope or None): the root of the HwComponent tree
            provided by the back-end. If None, it means the interface is not
            connected to a microscope (and displays a recorded acquisition).

        """

        self.microscope = microscope
        self.role = None

        # The following attributes are either HwComponents or None (if not available)
        self.ccd = None
        self.stage = None
        self.focus = None  # actuator to change the camera focus
        self.aligner = None  # actuator to align ebeam/ccd
        self.mirror = None  # actuator to change the mirror position (on SPARC)
        self.light = None
        self.light_filter = None  # emission light filter for SECOM/output filter for SPARC
        self.lens = None
        self.ebeam = None
        self.ebeam_focus = None  # change the e-beam focus
        self.sed = None  # secondary electron detector
        self.bsd = None  # backscattered electron detector
        self.spectrometer = None  # spectrometer
        self.spectrograph = None  # actuator to change the wavelength
        self.ar_spec_sel = None  # actuator to select AR/Spectrometer (SPARC)
        self.lens_switch = None  # actuator to (de)activate the lens (SPARC)
        self.chamber = None  # actuator to control the chamber (has vacuum, pumping etc.)
        self.chamber_ccd = None  # view of inside the chamber
        self.chamber_light = None  # Light illuminating the chamber
        self.overview_ccd = None  # global view from above the sample
        self.overview_focus = None  # focus of the overview CCD
        self.overview_light = None  # light of the overview CCD

        # Indicates whether the microscope is acquiring a high quality image
        self.is_acquiring = model.BooleanVA(False)

        # The microscope object will be probed for common detectors, actuators, emitters etc.
        if microscope:
            self.role = microscope.role

            for c in microscope.children.value:
                if c.role == "ccd":
                    self.ccd = c
                elif c.role == "se-detector":
                    self.sed = c
                elif c.role == "bs-detector":
                    self.bsd = c
                elif c.role == "spectrometer":
                    self.spectrometer = c
                elif c.role == "chamber-ccd":
                    self.chamber_ccd = c
                elif c.role == "overview-ccd":
                    self.overview_ccd = c
                elif c.role == "stage":
                    self.stage = c  # most views move this actuator when moving
                elif c.role == "focus":
                    self.focus = c
                elif c.role == "ebeam-focus":
                    self.ebeam_focus = c
                elif c.role == "overview-focus":
                    self.overview_focus = c
                elif c.role == "mirror":
                    self.mirror = c
                elif c.role == "align":
                    self.aligner = c
                elif c.role == "lens-switch":
                    self.lens_switch = c
                elif c.role == "ar-spec-selector":
                    self.ar_spec_sel = c
                elif c.role == "chamber":
                    self.chamber = c
                elif c.role == "light":
                    self.light = c
                elif c.role == "filter":
                    self.light_filter = c
                elif c.role == "lens":
                    self.lens = c
                elif c.role == "e-beam":
                    self.ebeam = c
                elif c.role == "chamber-light":
                    self.chamber_light = c
                elif c.role == "overview-light":
                    self.overview_light = c

            # Spectrograph is not directly an actuator, but a sub-comp of spectrometer
            if self.spectrometer:
                for child in self.spectrometer.children.value:
                    if child.role == "spectrograph":
                        self.spectrograph = child

            # Check that the components that can be expected to be present on an actual microscope
            # have been correctly detected.

            if not any((self.ccd, self.sed, self.bsd, self.spectrometer)):
                raise KeyError("No detector found in the microscope")

            if not self.light and not self.ebeam:
                raise KeyError("No emitter found in the microscope")

        # Chamber is complex so we provide a "simplified state"
        # It's managed by the ChamberController. Setting to PUMPING or VENTING
        # state will request a pressure change.
        chamber_states = {
            CHAMBER_UNKNOWN, CHAMBER_VENTED, CHAMBER_PUMPING, CHAMBER_VACUUM,
            CHAMBER_VENTING
        }
        self.chamberState = model.IntEnumerated(CHAMBER_UNKNOWN,
                                                chamber_states)

        # Used when doing fine alignment, based on the value used by the user
        # when doing manual alignment. 0.1s is not too bad value if the user
        # hasn't specified anything (yet).
        self.fineAlignDwellTime = FloatContinuous(0.1,
                                                  range=[1e-9, 100],
                                                  unit="s")

        # TODO: should we put also the configuration related stuff?
        # Like path/file format
        # Set to True to request debug info to be displayed
        self.debug = model.BooleanVA(False)

        # Current tab (+ all available tabs in choices as a dict tab -> name)
        # Fully set and managed later by the TabBarController.
        # Not very beautiful because Tab is not part of the model.
        # MicroscopyGUIData would be better in theory, but is less convenient
        # do directly access additional GUI information.
        self.tab = model.VAEnumerated(None, choices={None: ""})