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)
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()
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
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)
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
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
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)
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()
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)
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)
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: ""})
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: ""})