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)
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)
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
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 = {}
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
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
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")
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")
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
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)
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)
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()
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
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)
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'})
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()
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()
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_*
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)]
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)]
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)
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()
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)
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: ""})
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)
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)
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])
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: ""})