コード例 #1
0
ファイル: auto_align.py プロジェクト: lazem/odemis
    def __init__(self, microscope, main_app):
        super(AutomaticOverlayPlugin, self).__init__(microscope, main_app)
        self.addMenu("Data correction/Add && Align EM...", self.start)

        self._dlg = None

        # Projections of the reference and new data
        self._rem_proj = None
        self._nem_proj = None

        # On-the-fly keypoints and matching keypoints computed
        self._nem_kp = None
        self._nem_mkp = None
        self._rem_kp = None
        self._rem_mkp = None

        # im_ref.choices contains the streams and their name
        self.im_ref = model.VAEnumerated(None, choices={None: ""})
        self.blur_ref = model.IntContinuous(2, range=(0, 20), unit="px")
        self.blur = model.IntContinuous(5, range=(0, 20), unit="px")
        self.crop_top = model.IntContinuous(0, range=(0, 200), unit="px")
        self.crop_top.clip_on_range = True
        self.crop_bottom = model.IntContinuous(0, range=(0, 200), unit="px")
        self.crop_bottom.clip_on_range = True
        self.crop_left = model.IntContinuous(0, range=(0, 200), unit="px")
        self.crop_left.clip_on_range = True
        self.crop_right = model.IntContinuous(0, range=(0, 200), unit="px")
        self.crop_right.clip_on_range = True
        # TODO: inverting the values doesn't seem to really affect the keypoints
        self.invert = model.BooleanVA(False)
        # TODO: ideally, the flip shouldn't be needed, but it seems the matchers
        # in OpenCV are not able to handle "negative" scale
        self.flip_x = model.BooleanVA(False)
        self.flip_y = model.BooleanVA(False)
        self.draw_kp = model.BooleanVA(True)
        #         self.wta = model.IntContinuous(2, range=(2, 4))
        #         self.scaleFactor = model.FloatContinuous(1.2, range=(1.01, 2))
        #         self.nlevels = model.IntContinuous(8, range=(4, 48))
        #         self.patchSize = model.IntContinuous(31, range=(4, 256))

        # Any change on the VAs should update the stream
        self.blur_ref.subscribe(self._on_ref_stream)
        self.blur.subscribe(self._on_new_stream)
        self.crop_top.subscribe(self._on_new_stream)
        self.crop_bottom.subscribe(self._on_new_stream)
        self.crop_left.subscribe(self._on_new_stream)
        self.crop_right.subscribe(self._on_new_stream)
        self.invert.subscribe(self._on_new_stream)
        self.flip_x.subscribe(self._on_new_stream)
        self.flip_y.subscribe(self._on_new_stream)
        self.draw_kp.subscribe(self._on_draw_kp)
コード例 #2
0
    def __init__(self, microscope, main_app):
        super(TileAcqPlugin, self).__init__(microscope, main_app)

        self._dlg = None
        self._tab = None  # the acquisition tab
        self.ft = model.InstantaneousFuture()  # acquisition future
        self.microscope = microscope

        # Can only be used with a microscope
        if not microscope:
            return
        else:
            # Check if microscope supports tiling (= has a sample stage)
            main_data = self.main_app.main_data
            if main_data.stage:
                self.addMenu("Acquisition/Tile...\tCtrl+G", self.show_dlg)
            else:
                logging.info(
                    "Tile acquisition not available as no stage present")
                return

        self._ovrl_stream = None  # stream for fine alignment

        self.nx = model.IntContinuous(5, (1, 1000), setter=self._set_nx)
        self.ny = model.IntContinuous(5, (1, 1000), setter=self._set_ny)
        self.overlap = model.FloatContinuous(20, (-80, 80), unit="%")
        self.angle = model.FloatContinuous(0, (-90, 90), unit=u"°")
        self.filename = model.StringVA("a.ome.tiff")
        self.expectedDuration = model.VigilantAttribute(1,
                                                        unit="s",
                                                        readonly=True)
        self.totalArea = model.TupleVA((1, 1), unit="m", readonly=True)
        self.stitch = model.BooleanVA(True)
        self.fineAlign = model.BooleanVA(False)
        # TODO: manage focus (eg, autofocus or ask to manual focus on the corners
        # of the ROI and linearly interpolate)

        self.nx.subscribe(self._update_exp_dur)
        self.ny.subscribe(self._update_exp_dur)
        self.fineAlign.subscribe(self._update_exp_dur)
        self.nx.subscribe(self._update_total_area)
        self.ny.subscribe(self._update_total_area)
        self.overlap.subscribe(self._update_total_area)

        # Warn if memory will be exhausted
        self.nx.subscribe(self._memory_check)
        self.ny.subscribe(self._memory_check)
        self.stitch.subscribe(self._memory_check)
コード例 #3
0
 def __init__(self, name, detector, dataflow, emitter,
              ccd, stage, focus, shiftebeam=MTD_MD_UPD, **kwargs):
     """
     shiftebeam (MTD_*): if MTD_EBEAM_SHIFT, will correct the SEM position using beam shift
      (iow, using emitter.shift). If MTD_MD_UPD, it will just update the
      position correction metadata on the SEM images.
     ccd (Optical detector)
     stage (actuator): the sample stage, just to know when re-alignment is needed
     focus (actuator): the _optical_ focuser, just to know when re-alignment is needed
     focuser (actuator): the _e-beam_ focuser, to allow focusing the image
     """
     super(AlignedSEMStream, self).__init__(name, detector, dataflow, emitter, **kwargs)
     self._ccd = ccd
     self._stage = stage
     self._focus = focus
     self._shiftebeam = shiftebeam
     self.calibrated = model.BooleanVA(False)  # whether the calibration has been already done
     self._last_pos = stage.position.value.copy()
     self._last_pos.update(focus.position.value)  # last known position of the stage
     self._shift = (0, 0)  # (float, float): shift to apply in meters
     self._last_shift = (0, 0)  # (float, float): last ebeam shift applied
     # In case initialization takes place in unload position the
     # calibration values are not obtained yet. Thus we avoid to initialize
     # cur_trans before spot alignment takes place.
     self._cur_trans = None
     stage.position.subscribe(self._onMove)
     focus.position.subscribe(self._onMove)
     self._executor = ThreadPoolExecutor(max_workers=1)
     self._beamshift = None
コード例 #4
0
    def __init__(self, name, detector, emitter, spectrograph, opm=None):
        """
        name (string): user-friendly name of this stream
        detector (Detector): the monochromator
        emitter (Emitter): the emitter (eg: ebeam scanner)
        spectrograph (Actuator): the spectrograph
        """
        self.name = model.StringVA(name)

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

        self.is_active = model.BooleanVA(False)

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

        # For acquisition
        self._pt_acq = threading.Event()
        self._data = []
        self._md = {}
コード例 #5
0
    def __init__(self, microscope, main_app):
        super(TimelapsePlugin, self).__init__(microscope, main_app)
        # Can only be used with a microscope
        if not microscope:
            return

        self.period = model.FloatContinuous(10, (1e-3, 10000),
                                            unit="s",
                                            setter=self._setPeriod)
        # TODO: prevent period < acquisition time of all streams
        self.numberOfAcquisitions = model.IntContinuous(100, (2, 100000))
        self.semOnlyOnLast = model.BooleanVA(False)
        self.filename = model.StringVA("a.h5")
        self.expectedDuration = model.VigilantAttribute(1,
                                                        unit="s",
                                                        readonly=True)

        self.period.subscribe(self._update_exp_dur)
        self.numberOfAcquisitions.subscribe(self._update_exp_dur)

        # On SECOM/DELPHI, propose to only acquire the SEM at the end
        if microscope.role in ("secom", "delphi"):
            self.vaconf["semOnlyOnLast"][
                "control_type"] = odemis.gui.CONTROL_CHECK

        self._dlg = None
        self.addMenu("Acquisition/Timelapse...\tCtrl+T", self.start)

        self._to_store = queue.Queue(
        )  # queue of tuples (str, [DataArray]) for saving data
        self._sthreads = []  # the saving threads
        self._exporter = None  # dataio exporter to use
コード例 #6
0
    def __init__(self, name, detector, sed, emitter, opm=None):
        """
        name (string): user-friendly name of this stream
        detector (Detector): the monochromator
        sed (Detector): the se-detector
        emitter (Emitter): the emitter (eg: ebeam scanner)
        spectrograph (Actuator): the spectrograph
        """
        self.name = model.StringVA(name)

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

        self.is_active = model.BooleanVA(False)

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

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

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

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

        # For acquisition
        self.tc_data = None
        self.tc_data_received = threading.Event()
        self.sem_data = []
        self.sem_data_received = threading.Event()
        self._hw_settings = None
コード例 #7
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")
コード例 #8
0
ファイル: _live.py プロジェクト: arijitxx/odemis
    def __init__(self, name, detector, dataflow, emitter):
        Stream.__init__(self, name, detector, dataflow, emitter)

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

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

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

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

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

        # drift correction VAs:
        # dcRegion defines the anchor region, drift correction will be disabled
        #   if it is set to UNDEFINED_ROI
        # dcDwellTime: dwell time used when acquiring anchor region
        # dcPeriod is the (approximate) time between two acquisition of the
        #  anchor (and drift compensation). The exact period is determined so
        #  that it fits with the region of acquisition.
        # Note: the scale used for the acquisition of the anchor region is the
        #  same as the scale of the SEM. We could add a dcScale if it's needed.
        self.dcRegion = model.TupleContinuous(UNDEFINED_ROI,
                                              range=((0, 0, 0, 0), (1, 1, 1,
                                                                    1)),
                                              cls=(int, long, float),
                                              setter=self._setDCRegion)
        self.dcDwellTime = model.FloatContinuous(emitter.dwellTime.range[0],
                                                 range=emitter.dwellTime.range,
                                                 unit="s")
        self.dcPeriod = model.FloatContinuous(
            10,  # s, default to "fairly frequent" to work hopefully in most cases
            range=[0.1, 1e6],
            unit="s")
コード例 #9
0
ファイル: simulated.py プロジェクト: effting/odemis
    def __init__(self, name, role, positions, has_pressure=True, **kwargs):
        """
        Initialises the component
        positions (list of str): each pressure positions supported by the
          component (among the allowed ones)
        has_pressure (boolean): if True, has a pressure VA with the current
         pressure.
        """
        # TODO: or just provide .targetPressure (like .targetTemperature) ?
        # Or maybe provide .targetPosition: position that would be reached if
        # all the requested move were instantly applied?

        chp = {}
        for p in positions:
            try:
                chp[PRESSURES[p]] = p
            except KeyError:
                raise ValueError("Pressure position %s is unknown" % (p, ))
        axes = {"vacuum": model.Axis(unit="Pa", choices=chp)}
        model.Actuator.__init__(self, name, role, axes=axes, **kwargs)
        # For simulating moves
        self._position = PRESSURE_VENTED  # last official position
        self._goal = PRESSURE_VENTED
        self._time_goal = 0  # time the goal was/will be reached
        self._time_start = 0  # time the move started

        # RO, as to modify it the client must use .moveRel() or .moveAbs()
        self.position = model.VigilantAttribute({"vacuum": self._position},
                                                unit="Pa",
                                                readonly=True)
        if has_pressure:
            # Almost the same as position, but gives the current position
            self.pressure = model.VigilantAttribute(self._position,
                                                    unit="Pa",
                                                    readonly=True)

            self._press_timer = util.RepeatingTimer(
                1, self._updatePressure, "Simulated pressure update")
            self._press_timer.start()
        else:
            self._press_timer = None

        # Indicates whether the chamber is opened or not
        # Just pretend it's always closed, and allow the user to change that
        # for instance via CLI.
        self.opened = model.BooleanVA(False)

        # will take care of executing axis move asynchronously
        self._executor = CancellableThreadPoolExecutor(
            max_workers=1)  # one task at a time
コード例 #10
0
    def __init__(self,
                 name,
                 detector,
                 dataflow,
                 emitter,
                 l2=None,
                 analyzer=None,
                 **kwargs):
        """
        See SpectrumSettingsStream for the standard options
        l2 (None or Actuator with "x" axis): to move the lens 2 (aka "lens-switch")
        analyzer (None or Actuator with "pol" axis): the polarization analyzer.
          It should have at least the 7 "standard" positions
        """
        super(LASpectrumSettingsStream,
              self).__init__(name, detector, dataflow, emitter, **kwargs)

        self.l2 = l2
        if l2:
            # Convert the boolean to the actual position.
            # The actuator is expected to have two positions, named "on" and "off"
            self._toLens2Pos = None
            for pos, pos_name in l2.axes["x"].choices.items():
                if pos_name == "on":
                    self._toLens2Pos = pos
            if self._toLens2Pos is None:
                raise ValueError(
                    "Lens 2 actuator should have an 'on' position, but only %s"
                    % (list(l2.axes["x"].choices.values()), ))

        # Polarization stored on the stream.
        # We don't use the standard "global" axes trick, so that it's possible
        # to have multiple streams, each with a different polarization.
        self.analyzer = analyzer
        if analyzer:
            # Hardcode the 6 pol pos + pass-through
            positions = set(POL_POSITIONS) | {MD_POL_NONE}
            # check positions specified in the microscope file are correct
            for pos in positions:
                if pos not in analyzer.axes["pol"].choices:
                    raise ValueError(
                        "Polarization analyzer %s misses position '%s'" %
                        (analyzer, pos))
            self.polarization = model.VAEnumerated(MD_POL_NONE,
                                                   choices=positions)

            # Not used, but the MDStream expects it as well.
            self.acquireAllPol = model.BooleanVA(False)
コード例 #11
0
    def __init__(self, name):
        self._calibrated = None
        Stream.__init__(self, name, None, None, None)
        self.histogram._edges = (0, 0)

        minb, maxb = 0, 1  # unknown/unused
        pixel_width = 0.01

        self.centerWavelength = model.FloatContinuous((1 + minb) / 2,
                                                      range=(minb, maxb),
                                                      unit="m")
        max_bw = maxb - minb
        self.bandwidth = model.FloatContinuous(max_bw / 12,
                                               range=(pixel_width, max_bw),
                                               unit="m")

        self.fitToRGB = model.BooleanVA(True)
コード例 #12
0
    def __init__(self, stream):
        '''
        stream (Stream): the Stream to project
        '''
        super(RGBSpatialProjection, self).__init__(stream)

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

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

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

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

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

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

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

        self._shouldUpdateImage()
コード例 #13
0
    def __init__(self, name, role, parent, daemon=None, **kwargs):
        super(StreakUnit, self).__init__(name,
                                         role,
                                         parent=parent,
                                         daemon=daemon,
                                         **kwargs)  # init HwComponent

        self.parent = parent

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

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

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

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

        self._metadata[model.MD_STREAK_TIMERANGE] = self.timeRange.value
        self._metadata[model.MD_STREAK_MCPGAIN] = self.MCPGain.value
        self._metadata[model.MD_STREAK_MODE] = self.streakMode.value
コード例 #14
0
ファイル: focustracker.py プロジェクト: amuskens/odemis
    def __init__(self, name, role, channel, node_idx, **kwargs):
        """

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

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

        self._tracking_sdo = self.node.sdo['Controller On/ Off'][1]
        # set VAs only if there is hardware, channel = '' if no hardware
        self._metadata[model.MD_GAIN_P] = self._proportional_gain_sdo.raw
        self._metadata[model.MD_GAIN_I] = self._integral_gain_sdo.raw
        self._metadata[model.MD_GAIN_I] = self._derivative_gain_sdo.raw
        self.targetPosition = model.FloatContinuous(
            self._target_pos_sdo.raw,
            TARGET_POSITION_RANGE,
            unit="m",
            getter=self._get_target_pos,
            setter=self._set_target_pos)
        self.position = model.FloatVA(self._position_sdo.raw,
                                      unit="m",
                                      readonly=True,
                                      getter=self._get_position)
        self.tracking = model.BooleanVA(self._tracking_sdo.raw,
                                        getter=self._get_tracking,
                                        setter=self._set_tracking)
コード例 #15
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'})
コード例 #16
0
    def __init__(self, name, image):
        """
        name (string)
        image (model.DataArray of shape (CYX) or (C11YX)). The metadata
        MD_WL_POLYNOMIAL should be included in order to associate the C to a
        wavelength.
        """
        self._calibrated = None  # just for the _updateDRange to not complain
        Stream.__init__(self, name, None, None, None)
        # Spectrum stream has in addition to normal stream:
        #  * information about the current bandwidth displayed (avg. spectrum)
        #  * coordinates of 1st point (1-point, line)
        #  * coordinates of 2nd point (line)

        if len(image.shape) == 3:
            # force 5D
            image = image[:, numpy.newaxis, numpy.newaxis, :, :]
        elif len(image.shape) != 5 or image.shape[1:3] != (1, 1):
            logging.error("Cannot handle data of shape %s", image.shape)
            raise NotImplementedError("SpectrumStream needs a cube data")

        # ## this is for "average spectrum" projection
        try:
            # cached list of wavelength for each pixel pos
            self._wl_px_values = spectrum.get_wavelength_per_pixel(image)
        except (ValueError, KeyError):
            # useless polynomial => just show pixels values (ex: -50 -> +50 px)
            # TODO: try to make them always int?
            max_bw = image.shape[0] // 2
            min_bw = (max_bw - image.shape[0]) + 1
            self._wl_px_values = range(min_bw, max_bw + 1)
            assert (len(self._wl_px_values) == image.shape[0])
            unit_bw = "px"
            cwl = (max_bw + min_bw) // 2
            width = image.shape[0] // 12
        else:
            min_bw, max_bw = self._wl_px_values[0], self._wl_px_values[-1]
            unit_bw = "m"
            cwl = (max_bw + min_bw) / 2
            width = (max_bw - min_bw) / 12

        # TODO: allow to pass the calibration data as argument to avoid
        # recomputing the data just after init?
        # Spectrum efficiency compensation data: None or a DataArray (cf acq.calibration)
        self.efficiencyCompensation = model.VigilantAttribute(
            None, setter=self._setEffComp)

        # The background data (typically, an acquisition without ebeam).
        # It is subtracted from the acquisition data.
        # If set to None, a simple baseline background value is subtracted.
        self.background = model.VigilantAttribute(None,
                                                  setter=self._setBackground)

        # low/high values of the spectrum displayed
        self.spectrumBandwidth = model.TupleContinuous(
            (cwl - width, cwl + width),
            range=((min_bw, min_bw), (max_bw, max_bw)),
            unit=unit_bw,
            cls=(int, long, float))

        # Whether the (per bandwidth) display should be split intro 3 sub-bands
        # which are applied to RGB
        self.fitToRGB = model.BooleanVA(False)

        self._drange = None

        # This attribute is used to keep track of any selected pixel within the
        # data for the display of a spectrum
        self.selected_pixel = model.TupleVA((None, None))  # int, int

        # first point, second point in pixels. It must be 2 elements long.
        self.selected_line = model.ListVA([(None, None), (None, None)],
                                          setter=self._setLine)

        # The thickness of a point of a line (shared).
        # A point of width W leads to the average value between all the pixels
        # which are within W/2 from the center of the point.
        # A line of width W leads to a 1D spectrum taking into account all the
        # pixels which fit on an orthogonal line to the selected line at a
        # distance <= W/2.
        self.width = model.IntContinuous(1, [1, 50], unit="px")

        self.fitToRGB.subscribe(self.onFitToRGB)
        self.spectrumBandwidth.subscribe(self.onSpectrumBandwidth)
        self.efficiencyCompensation.subscribe(self._onCalib)
        self.background.subscribe(self._onCalib)

        self.raw = [image
                    ]  # for compatibility with other streams (like saving...)
        self._calibrated = image  # the raw data after calibration

        self._updateDRange()
        self._updateHistogram()
        self._updateImage()
コード例 #17
0
    def __init__(self, name, role, parent, hfw_nomag, **kwargs):
        model.Emitter.__init__(self, name, role, parent=parent, **kwargs)
        self._hfw_nomag = hfw_nomag

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

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

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

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

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

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

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

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

        # Refresh regularly the values, from the hardware, starting from now
        self._updateSettings()
        self._va_poll = util.RepeatingTimer(5, self._updateSettings,
                                            "Settings polling")
        self._va_poll.start()
コード例 #18
0
ファイル: _base.py プロジェクト: arijitxx/odemis
    def __init__(self, name, detector, dataflow, emitter):
        """
        name (string): user-friendly name of this stream
        detector (Detector): the detector which has the dataflow
        dataflow (Dataflow): the dataflow from which to get the data
        emitter (Emitter): the emitter
        """

        self.name = model.StringVA(name)

        # Hardware Components
        self._detector = detector
        self._emitter = emitter

        # Dataflow (Live image stream with meta data)
        # Note: A Detectors can have multiple dataflows, so that's why a Stream
        # has a separate attribute.
        self._dataflow = dataflow

        # TODO: this flag is horrendous as it can lead to not updating the image
        # with the latest image. We need to reorganise everything so that the
        # image display is done via a dataflow (in a separate thread), instead
        # of a VA.
        self._running_upd_img = False  # to avoid simultaneous updates in different threads
        # list of DataArray received and used to generate the image
        # every time it's modified, image is also modified
        self.raw = []
        # the most important attribute
        self.image = model.VigilantAttribute(None)

        # TODO: should maybe to 2 methods activate/deactivate to explicitly
        # start/stop acquisition, and one VA "updated" to stated that the user
        # want this stream updated (as often as possible while other streams are
        # also updated)
        # should_update has no effect direct effect, it's just a flag to
        # indicate the user would like to have the stream updated (live)
        self.should_update = model.BooleanVA(False)
        # is_active set to True will keep the acquisition going on
        self.is_active = model.BooleanVA(False)
        self.is_active.subscribe(self.onActive)

        # Region of interest as left, top, right, bottom (in ratio from the
        # whole area of the emitter => between 0 and 1)
        self.roi = model.TupleContinuous((0, 0, 1, 1),
                                         range=((0, 0, 0, 0), (1, 1, 1, 1)),
                                         cls=(int, long, float))

        self._drange = None  # min/max data range, or None if unknown

        # whether to use auto brightness & contrast
        self.auto_bc = model.BooleanVA(True)
        # % of values considered outliers discarded in auto BC detection
        # Note: 1/256th is a nice value because on RGB, it means in degenerated
        # cases (like flat histogram), you still loose only one value on each
        # side.
        self.auto_bc_outliers = model.FloatContinuous(100 / 256, range=(0, 40))

        # Used if auto_bc is False
        # min/max ratio of the whole intensity level which are mapped to
        # black/white. Its range is ._drange (will be updated by _updateDRange)
        self.intensityRange = model.TupleContinuous(
            (0, 0),
            range=((0, 0), (1, 1)),
            cls=(int, long, float),
            setter=self._setIntensityRange)
        # Make it so that the value gets clipped when its range is updated and
        # the value is outside of it.
        self.intensityRange.clip_on_range = True
        self._updateDRange()

        # Histogram of the current image _or_ slightly older image.
        # Note it's an ndarray. Use .tolist() to get a python list.
        self.histogram = model.VigilantAttribute(numpy.empty(0), readonly=True)
        self.histogram._full_hist = numpy.ndarray(
            0)  # for finding the outliers
        self.histogram._edges = None

        self.auto_bc.subscribe(self._onAutoBC)
        self.auto_bc_outliers.subscribe(self._onOutliers)
        self.intensityRange.subscribe(self._onIntensityRange)
        self._ht_needs_recompute = threading.Event()
        self._hthread = threading.Thread(target=self._histogram_thread,
                                         name="Histogram computation")
        self._hthread.daemon = True
        self._hthread.start()

        # self.histogram.subscribe(self._onHistogram) # FIXME -> update outliers and then image

        # list of warnings to display to the user
        # TODO should be a set
        self.warnings = model.ListVA([])  # should only contain WARNING_*
コード例 #19
0
ファイル: _static.py プロジェクト: ihebdelmic/odemis
    def __init__(self, name, image, *args, **kwargs):
        """
        name (string)
        image (model.DataArray(Shadow) of shape (CYX), (C11YX), (CTYX), (CT1YX), (1T1YX)).
        The metadata MD_WL_POLYNOMIAL or MD_WL_LIST should be included in order to
        associate the C to a wavelength.
        The metadata MD_TIME_LIST should be included to associate the T to a timestamp

        .background is a DataArray of shape (CT111), where C & T have the same length as in the data.
        .efficiencyCompensation is always DataArray of shape C1111.

        """
        # Spectrum stream has in addition to normal stream:
        #  * information about the current bandwidth displayed (avg. spectrum)
        #  * coordinates of 1st point (1-point, line)
        #  * coordinates of 2nd point (line)

        # TODO: need to handle DAS properly, in case it's tiled (in XY), to avoid
        # loading too much data in memory.
        # Ensure the data is a DataArray, as we don't handle (yet) DAS
        if isinstance(image, model.DataArrayShadow):
            image = image.getData()

        if len(image.shape) == 3:
            # force 5D for CYX
            image = image[:, numpy.newaxis, numpy.newaxis, :, :]
        elif len(image.shape) == 4:
            # force 5D for CTYX
            image = image[:, :, numpy.newaxis, :, :]
        elif len(image.shape) != 5 or image.shape[2] != 1:
            logging.error("Cannot handle data of shape %s", image.shape)
            raise NotImplementedError(
                "StaticSpectrumStream needs 3D or 4D data")

        # This is for "average spectrum" projection
        # cached list of wavelength for each pixel pos
        self._wl_px_values, unit_bw = spectrum.get_spectrum_range(image)
        min_bw, max_bw = self._wl_px_values[0], self._wl_px_values[-1]
        cwl = (max_bw + min_bw) / 2
        width = (max_bw - min_bw) / 12

        # The selected wavelength for a temporal spectrum display
        self.selected_wavelength = model.FloatContinuous(
            self._wl_px_values[0],
            range=(min_bw, max_bw),
            unit=unit_bw,
            setter=self._setWavelength)

        # Is there time data?
        if image.shape[1] > 1:
            # cached list of timestamps for each position in the time dimension
            self._tl_px_values, unit_t = spectrum.get_time_range(image)
            min_t, max_t = self._tl_px_values[0], self._tl_px_values[-1]

            # Allow the select the time as any value within the range, and the
            # setter will automatically "snap" it to the closest existing timestamp
            self.selected_time = model.FloatContinuous(self._tl_px_values[0],
                                                       range=(min_t, max_t),
                                                       unit=unit_t,
                                                       setter=self._setTime)

        # This attribute is used to keep track of any selected pixel within the
        # data for the display of a spectrum
        self.selected_pixel = model.TupleVA((None, None))  # int, int

        # first point, second point in pixels. It must be 2 elements long.
        self.selected_line = model.ListVA([(None, None), (None, None)],
                                          setter=self._setLine)

        # The thickness of a point or a line (shared).
        # A point of width W leads to the average value between all the pixels
        # which are within W/2 from the center of the point.
        # A line of width W leads to a 1D spectrum taking into account all the
        # pixels which fit on an orthogonal line to the selected line at a
        # distance <= W/2.
        self.selectionWidth = model.IntContinuous(1, [1, 50], unit="px")
        self.selectionWidth.subscribe(self._onSelectionWidth)

        # Peak method index, None if spectrum peak fitting curve is not displayed
        self.peak_method = model.VAEnumerated("gaussian",
                                              {"gaussian", "lorentzian", None})

        # TODO: allow to pass the calibration data as argument to avoid
        # recomputing the data just after init?
        # Spectrum efficiency compensation data: None or a DataArray (cf acq.calibration)
        self.efficiencyCompensation = model.VigilantAttribute(
            None, setter=self._setEffComp)
        self.efficiencyCompensation.subscribe(self._onCalib)

        # Is there spectrum data?
        if image.shape[0] > 1:
            # low/high values of the spectrum displayed
            self.spectrumBandwidth = model.TupleContinuous(
                (cwl - width, cwl + width),
                range=((min_bw, min_bw), (max_bw, max_bw)),
                unit=unit_bw,
                cls=(int, long, float))
            self.spectrumBandwidth.subscribe(self.onSpectrumBandwidth)

            # Whether the (per bandwidth) display should be split intro 3 sub-bands
            # which are applied to RGB
            self.fitToRGB = model.BooleanVA(False)
            self.fitToRGB.subscribe(self.onFitToRGB)

        # the raw data after calibration
        self.calibrated = model.VigilantAttribute(image)

        if "acq_type" not in kwargs:
            if image.shape[0] > 1 and image.shape[1] > 1:
                kwargs["acq_type"] = model.MD_AT_TEMPSPECTRUM
            elif image.shape[0] > 1:
                kwargs["acq_type"] = model.MD_AT_SPECTRUM
            elif image.shape[1] > 1:
                kwargs["acq_type"] = model.MD_AT_TEMPORAL
            else:
                logging.warning(
                    "SpectrumStream data has no spectrum or time dimension, shape = %s",
                    image.shape)

        super(StaticSpectrumStream, self).__init__(name, [image], *args,
                                                   **kwargs)

        # Automatically select point/line if data is small (can only be done
        # after .raw is set)
        if image.shape[-2:] == (1,
                                1):  # Only one point => select it immediately
            self.selected_pixel.value = (0, 0)
        elif image.shape[
                -2] == 1:  # Horizontal line => select line immediately
            self.selected_line.value = [(0, 0), (image.shape[-1] - 1, 0)]
        elif image.shape[-1] == 1:  # Vertical line => select line immediately
            self.selected_line.value = [(0, 0), (0, image.shape[-2] - 1)]
コード例 #20
0
    def __init__(self, name, image):
        """
        name (string)
        image (model.DataArray(Shadow) of shape (CYX) or (C11YX)). The metadata
        MD_WL_POLYNOMIAL or MD_WL_LIST should be included in order to associate the C to a
        wavelength.
        """
        # Spectrum stream has in addition to normal stream:
        #  * information about the current bandwidth displayed (avg. spectrum)
        #  * coordinates of 1st point (1-point, line)
        #  * coordinates of 2nd point (line)

        # TODO: need to handle DAS properly, in case it's tiled (in XY), to avoid
        # loading too much data in memory.
        # Ensure the data is a DataArray, as we don't handle (yet) DAS
        if isinstance(image, model.DataArrayShadow):
            image = image.getData()

        if len(image.shape) == 3:
            # force 5D
            image = image[:, numpy.newaxis, numpy.newaxis, :, :]
        elif len(image.shape) != 5 or image.shape[1:3] != (1, 1):
            logging.error("Cannot handle data of shape %s", image.shape)
            raise NotImplementedError("SpectrumStream needs a cube data")

        # This is for "average spectrum" projection
        try:
            # cached list of wavelength for each pixel pos
            self._wl_px_values = spectrum.get_wavelength_per_pixel(image)
        except (ValueError, KeyError):
            # useless polynomial => just show pixels values (ex: -50 -> +50 px)
            # TODO: try to make them always int?
            max_bw = image.shape[0] // 2
            min_bw = (max_bw - image.shape[0]) + 1
            self._wl_px_values = range(min_bw, max_bw + 1)
            assert(len(self._wl_px_values) == image.shape[0])
            unit_bw = "px"
            cwl = (max_bw + min_bw) // 2
            width = image.shape[0] // 12
        else:
            min_bw, max_bw = self._wl_px_values[0], self._wl_px_values[-1]
            unit_bw = "m"
            cwl = (max_bw + min_bw) / 2
            width = (max_bw - min_bw) / 12

        # TODO: allow to pass the calibration data as argument to avoid
        # recomputing the data just after init?
        # Spectrum efficiency compensation data: None or a DataArray (cf acq.calibration)
        self.efficiencyCompensation = model.VigilantAttribute(None, setter=self._setEffComp)

        # The background data (typically, an acquisition without e-beam).
        # It is subtracted from the acquisition data.
        # If set to None, a simple baseline background value is subtracted.
        self.background = model.VigilantAttribute(None, setter=self._setBackground)

        # low/high values of the spectrum displayed
        self.spectrumBandwidth = model.TupleContinuous(
                                    (cwl - width, cwl + width),
                                    range=((min_bw, min_bw), (max_bw, max_bw)),
                                    unit=unit_bw,
                                    cls=(int, long, float))

        # Whether the (per bandwidth) display should be split intro 3 sub-bands
        # which are applied to RGB
        self.fitToRGB = model.BooleanVA(False)

        # This attribute is used to keep track of any selected pixel within the
        # data for the display of a spectrum
        self.selected_pixel = model.TupleVA((None, None))  # int, int

        # first point, second point in pixels. It must be 2 elements long.
        self.selected_line = model.ListVA([(None, None), (None, None)], setter=self._setLine)

        # Peak method index, None if spectrum peak fitting curve is not displayed
        self.peak_method = model.VAEnumerated("gaussian", {"gaussian", "lorentzian", None})

        # The thickness of a point or a line (shared).
        # A point of width W leads to the average value between all the pixels
        # which are within W/2 from the center of the point.
        # A line of width W leads to a 1D spectrum taking into account all the
        # pixels which fit on an orthogonal line to the selected line at a
        # distance <= W/2.
        self.selectionWidth = model.IntContinuous(1, [1, 50], unit="px")

        self.fitToRGB.subscribe(self.onFitToRGB)
        self.spectrumBandwidth.subscribe(self.onSpectrumBandwidth)
        self.efficiencyCompensation.subscribe(self._onCalib)
        self.background.subscribe(self._onCalib)
        self.selectionWidth.subscribe(self._onSelectionWidth)

        self._calibrated = image  # the raw data after calibration
        super(StaticSpectrumStream, self).__init__(name, [image])

        # Automatically select point/line if data is small (can only be done
        # after .raw is set)
        if image.shape[-2:] == (1, 1):  # Only one point => select it immediately
            self.selected_pixel.value = (0, 0)
        elif image.shape[-2] == 1:  # Horizontal line => select line immediately
            self.selected_line.value = [(0, 0), (image.shape[-1] - 1, 0)]
        elif image.shape[-1] == 1:  # Vertical line => select line immediately
            self.selected_line.value = [(0, 0), (0, image.shape[-2] - 1)]
コード例 #21
0
    def __init__(self, microscope, main_app):
        super(QuickCLPlugin, self).__init__(microscope, main_app)
        # Can only be used with a SPARC with CL detector (or monochromator)
        if not microscope:
            return
        main_data = self.main_app.main_data
        if not main_data.ebeam or not (main_data.cld
                                       or main_data.monochromator):
            return

        self.conf = get_acqui_conf()
        self.filename = model.StringVA("")
        self.filename.subscribe(self._on_filename)

        self.expectedDuration = model.VigilantAttribute(1,
                                                        unit="s",
                                                        readonly=True)

        self.hasDatabar = model.BooleanVA(False)

        # Only put the VAs that do directly define the image as local, everything
        # else should be global. The advantage is double: the global VAs will
        # set the hardware even if another stream (also using the e-beam) is
        # currently playing, and if the VAs are changed externally, the settings
        # will be displayed correctly (and not reset the values on next play).
        emtvas = set()
        hwemtvas = set()
        for vaname in get_local_vas(main_data.ebeam,
                                    main_data.hw_settings_config):
            if vaname in ("resolution", "dwellTime", "scale"):
                emtvas.add(vaname)
            else:
                hwemtvas.add(vaname)

        self._sem_stream = stream.SEMStream(
            "Secondary electrons",
            main_data.sed,
            main_data.sed.data,
            main_data.ebeam,
            focuser=main_data.ebeam_focus,
            hwemtvas=hwemtvas,
            hwdetvas=None,
            emtvas=emtvas,
            detvas=get_local_vas(main_data.sed, main_data.hw_settings_config),
        )

        # This stream is used both for rendering and acquisition.
        # LiveCLStream is more or less like a SEMStream, but ensures the icon in
        # the merge slider is correct, and provide a few extra.
        if main_data.cld:
            self._cl_stream = LiveCLStream(
                "CL intensity",
                main_data.cld,
                main_data.cld.data,
                main_data.ebeam,
                focuser=main_data.ebeam_focus,
                emtvas=emtvas,
                detvas=get_local_vas(main_data.cld,
                                     main_data.hw_settings_config),
                opm=main_data.opm,
            )
            # TODO: allow to type in the resolution of the CL?
            # TODO: add the cl-filter axis (or reset it to pass-through?)
            self.logScale = self._cl_stream.logScale

            if hasattr(self._cl_stream, "detGain"):
                self._cl_stream.detGain.subscribe(self._on_cl_gain)

            # Update the acquisition time when it might change (ie, the scan settings
            # change)
            self._cl_stream.emtDwellTime.subscribe(self._update_exp_dur)
            self._cl_stream.emtResolution.subscribe(self._update_exp_dur)

        # Note: for now we don't really support SPARC with BOTH CL-detector and
        # monochromator.
        if main_data.monochromator:
            self._mn_stream = LiveCLStream(
                "Monochromator",
                main_data.monochromator,
                main_data.monochromator.data,
                main_data.ebeam,
                focuser=main_data.ebeam_focus,
                emtvas=emtvas,
                detvas=get_local_vas(main_data.monochromator,
                                     main_data.hw_settings_config),
                opm=main_data.opm,
            )
            self._mn_stream.emtDwellTime.subscribe(self._update_exp_dur)
            self._mn_stream.emtResolution.subscribe(self._update_exp_dur)

            # spg = self._getAffectingSpectrograph(main_data.spectrometer)
            # TODO: show axes

        self._dlg = None

        self.addMenu("Acquisition/Quick CL...\tF2", self.start)
コード例 #22
0
ファイル: pmtctrl.py プロジェクト: VictoriaDel/odemis
    def __init__(self,
                 name,
                 role,
                 port,
                 prot_time=1e-3,
                 prot_curr=30e-6,
                 relay_cycle=None,
                 powered=None,
                 **kwargs):
        '''
        port (str): port name
        prot_time (float): protection trip time (in s)
        prot_curr (float): protection current threshold (in Amperes)
        relay_cycle (None or 0<float): if not None, will power cycle the relay
          with the given delay (in s)
        powered (list of str or None): set of the HwComponents controlled by the relay
        Raise an exception if the device cannot be opened
        '''
        if powered is None:
            powered = []
        self.powered = powered

        model.PowerSupplier.__init__(self, name, role, **kwargs)

        # get protection time (s) and current (A) properties
        if not 0 <= prot_time < 1e3:
            raise ValueError("prot_time should be a time (in s) but got %s" %
                             (prot_time, ))
        self._prot_time = prot_time
        if not 0 <= prot_curr <= 100e-6:
            raise ValueError("prot_curr (%s A) is not between 0 and 100.e-6" %
                             (prot_curr, ))
        self._prot_curr = prot_curr

        # TODO: catch errors and convert to HwError
        self._ser_access = threading.Lock()

        self._port = self._findDevice(port)  # sets ._serial
        logging.info("Found PMT Control device on port %s", self._port)

        # Get identification of the PMT control device
        self._idn = self._getIdentification()

        driver_name = driver.getSerialDriver(self._port)
        self._swVersion = "serial driver: %s" % (driver_name, )
        self._hwVersion = "%s" % (self._idn, )

        # Set protection current and time
        self._setProtectionCurrent(self._prot_curr)
        self._setProtectionTime(self._prot_time)

        # gain, powerSupply and protection VAs
        self.protection = model.BooleanVA(True,
                                          setter=self._setProtection,
                                          getter=self._getProtection)
        self._setProtection(True)

        gain_rng = (MIN_VOLT, MAX_VOLT)
        gain = self._getGain()
        self.gain = model.FloatContinuous(gain,
                                          gain_rng,
                                          unit="V",
                                          setter=self._setGain)

        self.powerSupply = model.BooleanVA(True, setter=self._setPowerSupply)
        self._setPowerSupply(True)

        # will take care of executing supply asynchronously
        self._executor = CancellableThreadPoolExecutor(
            max_workers=1)  # one task at a time

        # relay initialization
        if relay_cycle is not None:
            logging.info("Power cycling the relay for %f s", relay_cycle)
            self.setRelay(False)
            time.sleep(relay_cycle)

        # Reset if no powered provided
        if not powered:
            self.setRelay(True)
        else:
            self._supplied = {}
            self.supplied = model.VigilantAttribute(self._supplied,
                                                    readonly=True)
            self._updateSupplied()
コード例 #23
0
ファイル: __init__.py プロジェクト: arijitxx/odemis
    def __init__(self, name, stage=None, focus=None, stream_classes=None):
        """
        :param name (string): user-friendly name of the view
        :param stage (Actuator): actuator with two axes: x and y
        :param focus (Actuator): actuator with one axis: z. Can be None
        :param stream_classes (None, or tuple of classes): all subclasses that the
          streams in this view is allowed to show.
        """

        super(StreamView, self).__init__(name)

        if stream_classes is None:
            self.stream_classes = (Stream, )
        else:
            self.stream_classes = stream_classes
        self._stage = stage

        # TODO: allow to have multiple focus, one per stream class
        self.focus = focus
        if focus is not None:
            self._focus_queue = Queue.Queue()
            self._focus_thread = threading.Thread(target=self._moveFocus,
                                                  name="Focus mover view %s" %
                                                  name)
            # TODO: way to detect the view is not used and so we need to stop the thread? (cf __del__?)
            self._focus_thread.daemon = True
            self._focus_thread.start()

        # The real stage position, to be modified via moveStageToView()
        # it's a direct access from the stage, so looks like a dict of axes
        if stage:
            self.stage_pos = stage.position

            # the current center of the view, which might be different from
            # the stage
            pos = self.stage_pos.value
            view_pos_init = (pos["x"], pos["y"])
        else:
            view_pos_init = (0, 0)

        self.view_pos = model.ListVA(view_pos_init, unit="m")

        # current density (meter per pixel, ~ scale/zoom level)
        # 1µm/px => ~large view of the sample (view width ~= 1000 px)
        self.mpp = FloatContinuous(1e-6, range=(10e-12, 50e-6), unit="m/px")

        # How much one image is displayed on the other one. Value used by
        # StreamTree
        self.merge_ratio = FloatContinuous(0.3, range=[0, 1], unit="")
        self.merge_ratio.subscribe(self._onMergeRatio)

        # Streams to display (can be considered an implementation detail in most
        # cases)
        # Note: use addStream/removeStream for simple modifications
        self.stream_tree = StreamTree(merge=self.merge_ratio.value)
        # Only modify with this lock acquired:
        # TODO: Is this the source of the intermittent locking of the GUI when
        # Streams are active? If so, is there another/better way?
        self._streams_lock = threading.Lock()

        # TODO: list of annotations to display
        self.show_crosshair = model.BooleanVA(True)
コード例 #24
0
ファイル: __init__.py プロジェクト: arijitxx/odemis
    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: ""})
コード例 #25
0
 def __init__(self, name, detector, dataflow, emitter, **kwargs):
     super(LiveCLStream, self).__init__(name, detector, dataflow, emitter,
                                        **kwargs)
     self.logScale = model.BooleanVA(False)
     self.logScale.subscribe(self._on_log_scale)
コード例 #26
0
ファイル: __init__.py プロジェクト: PierreBizouard/odemis
    def __init__(self,
                 name,
                 stage=None,
                 focus0=None,
                 focus1=None,
                 stream_classes=None):
        """
        :param name (string): user-friendly name of the view
        :param stage (Actuator): actuator with two axes: x and y
        :param focus0 (Actuator): actuator with one axis: z. Can be None
        :param focus1 (Actuator): actuator with one axis: z. Can be None
          Focuses 0 and 1 are modified when changing focus respectively along
          the X and Y axis.
        :param stream_classes (None, or tuple of classes): all subclasses that the
          streams in this view is allowed to show.
        """

        super(MicroscopeView, self).__init__(name)

        if stream_classes is None:
            self.stream_classes = (Stream, )
        else:
            self.stream_classes = stream_classes
        self._stage = stage
        self._focus = [focus0, focus1]

        # The real stage position, to be modified via moveStageToView()
        # it's a direct access from the stage, so looks like a dict of axes
        if stage:
            self.stage_pos = stage.position
            # stage.position.subscribe(self.onStagePos)

            # the current center of the view, which might be different from
            # the stage
            # TODO: we might need to have it on the MicroscopeModel, if all the
            # viewports must display the same location
            pos = self.stage_pos.value
            view_pos_init = (pos["x"], pos["y"])
        else:
            view_pos_init = (0, 0)

        self.view_pos = model.ListVA(view_pos_init, unit="m")

        # current density (meter per pixel, ~ scale/zoom level)
        # 10µm/px => ~large view of the sample
        self.mpp = FloatContinuous(10e-6, range=(10e-12, 1e-3), unit="m/px")

        # How much one image is displayed on the other one. Value used by
        # StreamTree
        self.merge_ratio = FloatContinuous(0.3, range=[0, 1], unit="")
        self.merge_ratio.subscribe(self._onMergeRatio)

        # Streams to display (can be considered an implementation detail in most
        # cases)
        # Note: use addStream/removeStream for simple modifications
        self.stream_tree = StreamTree(merge=self.merge_ratio.value)
        # Only modify with this lock acquired:
        # TODO: Is this the source of the intermittent locking of the GUI when
        # Streams are active? If so, is there another/better way?
        self._streams_lock = threading.Lock()

        # TODO: list of annotations to display
        self.show_crosshair = model.BooleanVA(True)
コード例 #27
0
ファイル: _base.py プロジェクト: amuskens/odemis
    def __init__(self, name, detector, dataflow, emitter, focuser=None, opm=None,
                 hwdetvas=None, hwemtvas=None, detvas=None, emtvas=None, raw=None,
                 acq_type=None):
        """
        name (string): user-friendly name of this stream
        detector (Detector): the detector which has the dataflow
        dataflow (Dataflow): the dataflow from which to get the data
        emitter (Emitter): the emitter
        opm (OpticalPathManager): the optical path manager
        focuser (Actuator or None): an actuator with a 'z' axis that allows to change
          the focus
        hwdetvas (None or set of str): names of all detector hardware VAs to be controlled by this
            Stream
        hwemtvas (None or set of str): names of all emitter hardware VAs to be controlled by this
            Stream
        detvas (None or set of str): names of all the detector VigilantAttributes
          (VAs) to be duplicated on the stream. They will be named .detOriginalName
        emtvas (None or set of str): names of all the emitter VAs to be
          duplicated on the stream. They will be named .emtOriginalName
        raw (None or list of DataArrays or DataArrayShadow): raw data to be used
          at initialisation. By default, it will contain no data.
        acq_type (MD_AT_*): acquisition type associated with this stream (as in model._metadata)
        """
        self.name = model.StringVA(name)
        self.acquisitionType = model.VigilantAttribute(acq_type)  # MD_ACQ_TYPE or None
        # for identification of the acquisition type associated with the stream

        # Hardware Components
        self._detector = detector
        self._emitter = emitter
        self._focuser = focuser
        self._opm = opm

        # Dataflow (Live image stream with meta data)
        # Note: A Detector can have multiple dataflows, so that's why a Stream
        # has a separate attribute.
        self._dataflow = dataflow

        # TODO: We need to reorganise everything so that the
        # image display is done via a dataflow (in a separate thread), instead
        # of a VA.
        self._im_needs_recompute = threading.Event()
        self._init_thread()

        # list of DataArray(Shadow) received and used to generate the image
        # every time it's modified, image is also modified
        if raw is None:
            self.raw = []
        else:
            self.raw = raw

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

        # TODO: should better be based on a BufferedDataFlow: subscribing starts
        # acquisition and sends (raw) data to whoever is interested. .get()
        # returns the previous or next image acquired.

        # indicating if stream has already been prepared
        self._prepared = False
        # TODO: should_update is a GUI stuff => move away from stream
        # should_update has no effect direct effect, it's just a flag to
        # indicate the user would like to have the stream updated (live)
        self.should_update = model.BooleanVA(False)
        # is_active set to True will keep the acquisition going on
        self.is_active = model.BooleanVA(False, setter=self._is_active_setter)

        # Leech to use during acquisition.
        # Note: for now only some streams actually use them (MDStreams*)
        self.leeches = []

        # Hardware VA that the stream is directly linked to
        self.hw_vas = {}
        self.hw_vas.update(self._getVAs(detector, hwdetvas or set()))
        self.hw_vas.update(self._getVAs(emitter, hwemtvas or set()))

        # Duplicate VA if requested
        self._hwvas = {}  # str (name of the proxied VA) -> original Hw VA
        self._hwvasetters = {}  # str (name of the proxied VA) -> setter
        self._lvaupdaters = {}  # str (name of the proxied VA) -> listener

        self._det_vas = self._duplicateVAs(detector, "det", detvas or set())
        self._emt_vas = self._duplicateVAs(emitter, "emt", emtvas or set())

        self._dRangeLock = threading.Lock()
        self._drange = None  # min/max data range, or None if unknown
        self._drange_unreliable = True  # if current values are a rough guess (based on detector)

        # drange_raw is the smaller (less zoomed) image of an pyramidal image. It is used
        # instead of the full image because it would be too slow or even impossible to read
        # the full data from the image to the memory. It is also not the tiles from the tiled
        # image, so the code for pyramidal and non-pyramidal images
        # that reads drange_raw is the same.
        # The drawback of not using the full image, is that some of the pixels are lost, so
        # maybe the max/min of the smaller image is different from the min/max of the full image.
        # And the histogram of both images will probably be a bit different also.
        if raw and isinstance(raw[0], model.DataArrayShadow):
            # if the image is pyramidal, use the smaller image
            drange_raw = self._getMergedRawImage(raw[0], raw[0].maxzoom)
        else:
            drange_raw = None

        # TODO: move to the DataProjection class
        self.auto_bc = model.BooleanVA(True)
        self.auto_bc.subscribe(self._onAutoBC)

        # % of values considered outliers discarded in auto BC detection
        # Note: 1/256th is a nice value because on RGB, it means in degenerated
        # cases (like flat histogram), you still loose only one value on each
        # side.
        self.auto_bc_outliers = model.FloatContinuous(100 / 256, range=(0, 40))
        self.auto_bc_outliers.subscribe(self._onOutliers)

        self.tint = model.ListVA((255, 255, 255), unit="RGB")  # 3-int R,G,B

        # Used if auto_bc is False
        # min/max ratio of the whole intensity level which are mapped to
        # black/white. Its range is ._drange (will be updated by _updateDRange)
        self.intensityRange = model.TupleContinuous((0, 0),
                                                    range=((0, 0), (1, 1)),
                                                    cls=(int, long, float),
                                                    setter=self._setIntensityRange)
        # Make it so that the value gets clipped when its range is updated and
        # the value is outside of it.
        self.intensityRange.clip_on_range = True
        self._updateDRange(drange_raw)  # sets intensityRange
        self._init_projection_vas()

        # Histogram of the current image _or_ slightly older image.
        # Note it's an ndarray. Use .tolist() to get a python list.
        self.histogram = model.VigilantAttribute(numpy.empty(0), readonly=True)
        self.histogram._full_hist = numpy.ndarray(0) # for finding the outliers
        self.histogram._edges = None

        # Tuple of (int, str) or (None, None): loglevel and message
        self.status = model.VigilantAttribute((None, None), readonly=True)

        # Background data, to be subtracted from the acquisition data before
        # projection. It should be the same shape and dtype as the acquisition
        # data, otherwise no subtraction will be performed. If None, nothing is
        # subtracted is applied.
        self.background = model.VigilantAttribute(None, setter=self._setBackground)
        self.background.subscribe(self._onBackground)

        # if there is already some data, update image with it
        # TODO: have this done by the child class, if needed.
        if self.raw:
            self._updateHistogram(drange_raw)
            self._onNewData(None, self.raw[0])
コード例 #28
0
ファイル: __init__.py プロジェクト: PierreBizouard/odemis
    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: ""})