def __init__(self, name, x, y, z, milling_angle, streams=None): """ :param name: (string) the feature name :param x: (float) the X axis of the feature position :param y: (float) the Y axis of the feature position :param z: (float) the Z axis of the feature position :param milling_angle: (float) angle used for milling (angle between the sample and the ion-beam, similar to the one in the chamber tab, not the actual Rx) :param streams: (List of StaticStream) list of acquired streams on this feature """ self.name = model.StringVA(name) # The 3D position of an interesting point in the site (Typically, the milling should happen around that # volume, never touching it.) self.pos = model.TupleContinuous( (x, y, z), range=((-1, -1, -1), (1, 1, 1)), cls=(int, float), ) # TODO: Check if negative milling angle is allowed if milling_angle <= 0: milling_angle = DEFAULT_MILLING_ANGLE logging.warning( f"Given milling angle {milling_angle} is negative, setting it to default {DEFAULT_MILLING_ANGLE}" ) self.milling_angle = model.FloatVA(milling_angle) self.status = model.StringVA(FEATURE_ACTIVE) # TODO: Handle acquired files self.streams = streams if streams is not None else model.ListVA()
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, microscope, main_app): super(MonoScanPlugin, self).__init__(microscope, main_app) # Can only be used on a Sparc with a monochromator if not microscope: return try: self.ebeam = model.getComponent(role="e-beam") self.mchr = model.getComponent(role="monochromator") self.sgrh = model.getComponent(role="spectrograph") except LookupError: logging.debug("No mochromator and spectrograph found, cannot use the plugin") return self.addMenu("Acquisition/Monochromator scan...", self.start) # the SEM survey stream (will be updated when showing the window) self._survey_s = None # Create a stream for monochromator scan self._mchr_s = MonochromatorScanStream("Spectrum", self.mchr, self.ebeam, self.sgrh) # The settings to be displayed in the dialog # Trick: we use the same VAs as the stream, so they are directly synchronised self.startWavelength = self._mchr_s.startWavelength self.endWavelength = self._mchr_s.endWavelength self.numberOfPixels = self._mchr_s.numberOfPixels self.dwellTime = self._mchr_s.dwellTime self.filename = model.StringVA("a.h5") self.expectedDuration = model.VigilantAttribute(1, unit="s", readonly=True) # Update the expected duration when values change self.dwellTime.subscribe(self._update_exp_dur) self.numberOfPixels.subscribe(self._update_exp_dur)
def __init__(self, tab_data, main_frame, settings_controller, roa, vas): """ tab_data (MicroscopyGUIData): the representation of the microscope GUI main_frame: (wx.Frame): the frame which contains the 4 viewports settings_controller (SettingsController) roa (VA): VA of the ROA vas (list of VAs): all the VAs which might affect acquisition (time) """ self._tab_data_model = tab_data self._main_data_model = tab_data.main self._main_frame = main_frame self._roa = roa self._vas = vas # For file selection self.conf = conf.get_acqui_conf() # for saving/restoring the settings self._settings_controller = settings_controller self._orig_settings = {} # Entry -> value to restore # TODO: this should be the date at which the user presses the acquire # button (or when the last settings were changed)! # At least, we must ensure it's a new date after the acquisition # is done. # Filename to save the acquisition self.filename = model.StringVA(self._get_default_filename()) self.filename.subscribe(self._onFilename, init=True) # For acquisition # a ProgressiveFuture if the acquisition is going on self.btn_acquire = self._main_frame.btn_sparc_acquire self.btn_change_file = self._main_frame.btn_sparc_change_file self.btn_cancel = self._main_frame.btn_sparc_cancel self.acq_future = None self.gauge_acq = self._main_frame.gauge_sparc_acq self.lbl_acqestimate = self._main_frame.lbl_sparc_acq_estimate self._acq_future_connector = None # TODO: share an executor with the whole GUI. self._executor = futures.ThreadPoolExecutor(max_workers=2) # Link buttons self.btn_acquire.Bind(wx.EVT_BUTTON, self.on_acquisition) self.btn_change_file.Bind(wx.EVT_BUTTON, self.on_change_file) self.btn_cancel.Bind(wx.EVT_BUTTON, self.on_cancel) self.gauge_acq.Hide() self._main_frame.Layout() # TODO: we need to be informed if the user closes suddenly the window # self.Bind(wx.EVT_CLOSE, self.on_close) # Event binding pub.subscribe(self.on_setting_change, 'setting.changed') # TODO We should also listen to repetition, in case it's modified after we've # received the new ROI. Or maybe always compute acquisition time a bit delayed? for va in vas: va.subscribe(self.onAnyVA) roa.subscribe(self.onROA, init=True)
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(CLAcqPlugin, self).__init__(microscope, main_app) # Can only be used with a microscope if not microscope: return else: # Check which stream the microscope supports main_data = self.main_app.main_data if not (main_data.ccd and main_data.ebeam): return self.exposureTime = main_data.ccd.exposureTime self.binning = main_data.ccd.binning # Trick to pass the component (ccd to binning_1d_from_2d()) self.vaconf["binning"]["choices"] = ( lambda cp, va, cf: gui.conf.util.binning_1d_from_2d( main_data.ccd, va, cf)) self.xres = model.IntContinuous(10, (1, 1000), unit="px") self.yres = model.IntContinuous(10, (1, 1000), unit="px") self.stepsize = model.FloatVA(1e-6, unit="m") # Just to show # Maximum margin is half the CCD FoV ccd_rect = get_ccd_fov(main_data.ccd) max_margin = max(ccd_rect[2] - ccd_rect[0], ccd_rect[3] - ccd_rect[1]) / 2 self.roi_margin = model.FloatContinuous(0, (0, max_margin), unit="m") self.filename = model.StringVA("a.tiff") self.xres.subscribe(self._update_stepsize) self.yres.subscribe(self._update_stepsize) self.addMenu("Acquisition/CL acquisition...", self.start)
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, microscope, main_app): super(ZStackPlugin, self).__init__(microscope, main_app) # Can only be used with a microscope main_data = self.main_app.main_data if not microscope or main_data.focus is None: return self.focus = main_data.focus self._zrange = self.focus.axes['z'].range zunit = self.focus.axes['z'].unit self._old_pos = self.focus.position.value z = max(self._zrange[0], min(self._old_pos['z'], self._zrange[1])) self.zstart = model.FloatContinuous(z, range=self._zrange, unit=zunit) self.zstep = model.FloatContinuous(1e-6, range=(-1e-5, 1e-5), unit=zunit, setter=self._setZStep) self.numberOfAcquisitions = model.IntContinuous(3, (2, 999), setter=self._setNumberOfAcquisitions) self.filename = model.StringVA("a.h5") self.expectedDuration = model.VigilantAttribute(1, unit="s", readonly=True) self.zstep.subscribe(self._update_exp_dur) self.numberOfAcquisitions.subscribe(self._update_exp_dur) # Two acquisition order possible: # * for each Z, all the streams (aka intertwined): Z exactly the same for each stream # * for each stream, the whole Z stack: Might be faster (if filter wheel used for changing wavelength) self._streams_intertwined = True if main_data.light_filter and len(main_data.light_filter.axes["band"].choices) > 1: logging.info("Filter-wheel detected, Z-stack will be acquired stream-per-stream") self._streams_intertwined = False self._acq_streams = None # previously folded streams, for optimisation self._dlg = None self.addMenu("Acquisition/ZStack...\tCtrl+B", self.start)
def __init__(self, name, ccd, emitter, emd): """ name (string): user-friendly name of this stream ccd (Camera): the ccd emitter (Emitter): the emitter (eg: ebeam scanner) emd (Detector): the SEM detector (eg: SED) """ self.name = model.StringVA(name) # Hardware Components self._detector = emd self._emitter = emitter self._ccd = ccd # 0.1s is a bit small, but the algorithm will automaticaly try with # longer dwell times if no spot is visible first. self.dwellTime = model.FloatContinuous(0.1, range=emitter.dwellTime.range, unit="s") # The number of points in the grid self.repetition = model.ResolutionVA( (4, 4), # good default range=((2, 2), (16, 16))) # Future generated by find_overlay self._overlay_future = None
def __init__(self, microscope, main_app): super(ZStackPlugin, self).__init__(microscope, main_app) # Can only be used with a microscope main_data = self.main_app.main_data if not microscope or main_data.focus is None: return self.focus = main_data.focus self._zrange = self.focus.axes['z'].range zunit = self.focus.axes['z'].unit self.old_pos = self.focus.position.value self.zstart = model.FloatContinuous(self.old_pos['z'], range=self._zrange, unit=zunit) self.zstep = model.FloatContinuous(1e-6, range=(-1e-5, 1e-5), unit=zunit, setter=self._setZStep) self.numberofAcquisitions = model.IntContinuous( 3, (2, 999), setter=self._setNumberOfAcquisitions) self.filename = model.StringVA("a.h5") self.expectedDuration = model.VigilantAttribute(1, unit="s", readonly=True) self.zstep.subscribe(self._update_exp_dur) self.numberofAcquisitions.subscribe(self._update_exp_dur) self._acq_streams = None # previously folded streams, for optimisation self._dlg = None self.addMenu("Acquisition/ZStack...\tCtrl+B", self.start)
def __init__(self, name, coordinates, roc, asm, multibeam, descanner, detector): """ :param name: (str) Name of the region of acquisition (ROA). It is the name of the megafield (id) as stored on the external storage. :param coordinates: (float, float, float, float) left, top, right, bottom, Bounding box coordinates of the ROA in [m]. The coordinates are in the sample carrier coordinate system, which corresponds to the component with role='stage'. :param roc: (FastEMROC) Corresponding region of calibration (ROC). :param asm: (technolution.AcquisitionServer) The acquisition server module component. :param multibeam: (technolution.EBeamScanner) The multibeam scanner component of the acquisition server module. :param descanner: (technolution.MirrorDescanner) The mirror descanner component of the acquisition server module. :param detector: (technolution.MPPC) The detector object to be used for collecting the image data. """ self.name = model.StringVA(name) self.coordinates = model.TupleContinuous(coordinates, range=((-1, -1, -1, -1), (1, 1, 1, 1)), cls=(int, float), unit='m') self.roc = model.VigilantAttribute(roc) self._asm = asm self._multibeam = multibeam self._descanner = descanner self._detector = detector # List of tuples(int, int) containing the position indices of each field to be acquired. # Automatically updated when the coordinates change. self.field_indices = [] self.coordinates.subscribe(self.on_coordinates, init=True)
def __init__(self, microscope, main_app): super(AveragePlugin, self).__init__(microscope, main_app) # Can only be used with a microscope if not microscope: return # Check which stream the microscope supports main_data = self.main_app.main_data if not main_data.ebeam: return self.addMenu("Acquisition/Averaged frame...", self.start) dt = main_data.ebeam.dwellTime dtrg = (dt.range[0], min(dt.range[1], 1)) self.dwellTime = model.FloatContinuous(dt.value, range=dtrg, unit=dt.unit) self.scale = main_data.ebeam.scale # Trick to pass the component (ebeam to binning_1d_from_2d()) self.vaconf["scale"]["choices"] = ( lambda cp, va, cf: odemis.gui.conf.util.binning_1d_from_2d( self.main_app.main_data.ebeam, va, cf)) self.resolution = main_data.ebeam.resolution # Just for info self.accumulations = model.IntContinuous(10, (1, 10000)) self.filename = model.StringVA("a.h5") self.expectedDuration = model.VigilantAttribute(1, unit="s", readonly=True) self.dwellTime.subscribe(self._update_exp_dur) self.accumulations.subscribe(self._update_exp_dur) self.scale.subscribe(self._update_exp_dur)
def __init__(self, name, streams): """ :param streams: ([Stream]) The streams to acquire. """ super(SECOMCLSEMMDStream, self).__init__(name, streams) self.filename = model.StringVA("a.tiff") self.firstOptImg = None # save the first optical image for display in analysis tab
def __init__(self, name, detector, dataflow, emitter): self.name = model.StringVA(name) # Hardware Components self._detector = detector # the spectrometer self._emitter = emitter # the e-beam # To acquire simultaneously other detector (ex: SEM secondary electrons) # a separate stream must be used, and the acquisition manager will take # care of doing both at the same time # data-flow of the spectrometer self._dataflow = dataflow self.raw = [] # to contain data during acquisition (from MD streams) # all the information needed to acquire an image (in addition to the # hardware component settings which can be directly set). # ROI + repetition is sufficient, but pixel size is nicer for the user # and allow us to ensure each pixel is square. (Non-square pixels are # not a problem for the hardware, but annoying to display data in normal # software). # We ensure in the setters that all the data is always consistent: # roi set: roi + pxs → repetition + roi + pxs # pxs set: roi + pxs → repetition + roi (small changes) # repetition set: repetition + roi + pxs → repetition + pxs + roi (small changes) # Region of interest as left, top, right, bottom (in ratio from the # whole area of the emitter => between 0 and 1) self.roi = model.TupleContinuous((0, 0, 1, 1), range=[(0, 0, 0, 0), (1, 1, 1, 1)], cls=(int, long, float), setter=self._setROI) # the number of pixels acquired in each dimension # it will be assigned to the resolution of the emitter (but cannot be # directly set, as one might want to use the emitter while configuring # the stream). self.repetition = model.ResolutionVA(emitter.resolution.value, emitter.resolution.range, setter=self._setRepetition) # the size of the pixel, horizontally and vertically # actual range is dynamic, as it changes with the magnification self.pixelSize = model.FloatContinuous(emitter.pixelSize.value[0], range=[0, 1], unit="m", setter=self._setPixelSize) # exposure time of each pixel is the exposure time of the detector, # the dwell time of the emitter will be adapted before acquisition. # Update the pixel size whenever SEM magnification changes # This allows to keep the ROI at the same place in the SEM FoV. # Note: this is to be done only if the user needs to manually update the # magnification. self._prev_mag = emitter.magnification.value emitter.magnification.subscribe(self._onMagnification)
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, microscope, main_app): # Called when the plugin is loaed (ie, at GUI initialisation) super(Correlator2D, self).__init__(microscope, main_app) if not microscope or microscope.role not in ("sparc", "sparc2"): return try: self.ebeam = model.getComponent(role="e-beam") self.correlator = model.getComponent(role="time-correlator") self.sed = model.getComponent(role="se-detector") except LookupError: logging.debug("Hardware not found, cannot use the plugin") return # the SEM survey stream (will be updated when showing the window) self._survey_s = None # Create a stream for correlator spectral measurement self._correlator_s = CorrelatorScanStream("Correlator data", self.correlator, self.sed, self.ebeam, main_app.main_data.opm) # For reading the ROA and anchor ROI self._acqui_tab = main_app.main_data.getTabByName( "sparc_acqui").tab_data_model self.dwellTime = self._correlator_s.dwellTime self.pixelDuration = self._correlator_s.pixelDuration self.syncOffset = self._correlator_s.syncOffset self.syncDiv = self._correlator_s.syncDiv # The scanning positions are defined by ROI (as selected in the acquisition tab) + stepsize # Based on these values, the scanning resolution is computed self.roi = self._correlator_s.roi self.stepsize = self._correlator_s.stepsize self.cropvalue = self._correlator_s.cropvalue self.xres = model.IntVA(1, unit="px") self.yres = model.IntVA(1, unit="px") self.nDC = self._correlator_s.nDC 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) self.addMenu("Acquisition/CL time correlator scan...", self.start)
def __init__(self, name): self.name = model.StringVA(name) # a thumbnail version of what is displayed self.thumbnail = VigilantAttribute(None) # contains a wx.Image # Last time the image of the view was changed. It's actually mostly # a trick to allow other parts of the GUI to know when the (theoretical) # composited image has changed. self.lastUpdate = model.FloatVA(time.time(), unit="s")
def __init__(self, microscope, main_app): super(RGBCLIntensity, self).__init__(microscope, main_app) # Can only be used on a SPARC with a CL-intensity detector if not microscope: return try: self.ebeam = model.getComponent(role="e-beam") self.cldetector = model.getComponent(role="cl-detector") self.filterwheel = model.getComponent(role="cl-filter") self.sed = model.getComponent(role="se-detector") # We could also check the filter wheel has at least 3 filters, but # let's not be too picky, if the user has installed the plugin, he # probably wants to use it anyway. except LookupError: logging.info("Hardware not found, cannot use the RGB CL plugin") return # The SEM survey and CLi stream (will be updated when showing the window) self._survey_s = None self._cl_int_s = None self._acqui_tab = main_app.main_data.getTabByName( "sparc_acqui").tab_data_model # The settings to be displayed in the dialog # TODO: pick better default filters than first 3 filters # => based on the wavelengths fitting best RGB, or the names (eg, "Blue"), # and avoid "pass-through". fbchoices = self.filterwheel.axes["band"].choices if isinstance(fbchoices, dict): fbvalues = sorted(fbchoices.keys()) else: fbvalues = fbchoices # FloatEnumerated because filter positions can be in rad (ie, not int positions) self.filter1 = model.FloatEnumerated(fbvalues[0], choices=fbchoices) self.filter2 = model.FloatEnumerated(fbvalues[min( 1, len(fbvalues) - 1)], choices=fbchoices) self.filter3 = model.FloatEnumerated(fbvalues[min( 2, len(fbvalues) - 1)], choices=fbchoices) self._filters = [self.filter1, self.filter2, self.filter3] self._colours = [(0, 0, 255), (0, 255, 0), (255, 0, 0)] # B, G, R self.filename = model.StringVA("a.tiff") self.expectedDuration = model.VigilantAttribute(1, unit="s", readonly=True) self.addMenu("Acquisition/RGB CL intensity...", self.start)
def __init__(self, name, coordinates): """ :param name: (str) name of the ROC :param coordinates: (float, float, float, float) l, t, r, b coordinates in m """ self.name = model.StringVA(name) self.coordinates = model.TupleContinuous(coordinates, range=((-1, -1, -1, -1), (1, 1, 1, 1)), cls=(int, float), unit='m') self.parameters = None # calibration object with all relevant parameters
def __init__(self, microscope, main_app): super(SimplePlugin, self).__init__(microscope, main_app) # if not microscope: # return self.main_data = self.main_app.main_data # if not self.main_data.ccd: # return self.addMenu("Acquisition/Fancy acquisition...", self.start) self.exposureTime = model.FloatContinuous(2, (0, 10), unit="s") self.filename = model.StringVA("boo.h5")
def __init__(self, name, coordinates, roc): """ :param name: (str) name of the ROA :param coordinates: (float, float, float, float) l, t, r, b coordinates in m :param roc: (FastEMROC) corresponding region of calibration """ self.name = model.StringVA(name) self.coordinates = model.TupleContinuous(coordinates, range=((-1, -1, -1, -1), (1, 1, 1, 1)), cls=(int, float), unit='m') self.roc = model.VigilantAttribute(roc)
def __init__(self, microscope, main_app): super(MergeChannelsPlugin, self).__init__(microscope, main_app) self.filenameR = model.StringVA(" ") self.filenameG = model.StringVA(" ") self.filenameB = model.StringVA(" ") self.redShiftX = model.FloatContinuous(0, range=(-500, 500), unit="px") self.redShiftY = model.FloatContinuous(0, range=(-500, 500), unit="px") self.greenShiftX = model.FloatContinuous(0, range=(-500, 500), unit="px") self.greenShiftY = model.FloatContinuous(0, range=(-500, 500), unit="px") self.blueShiftX = model.FloatContinuous(0, range=(-500, 500), unit="px") self.blueShiftY = model.FloatContinuous(0, range=(-500, 500), unit="px") self.cropBottom = model.IntContinuous(0, range=(0, 200), unit="px") analysis_tab = self.main_app.main_data.getTabByName('analysis') analysis_tab.stream_bar_controller.add_action("Add RGB channels...", self.start) self.filenameR.subscribe(self._filenameR) self.filenameG.subscribe(self._filenameG) self.filenameB.subscribe(self._filenameB) self.cropBottom.subscribe(self._cropBottom) self._subscribers = [] self._dlg = None self._stream_red = None self._stream_green = None self._stream_blue = None self._raw_orig = { } # dictionary (Stream -> DataArray) to handle the (un)cropping
def __init__(self, name, coordinates, roc_2, roc_3, asm, multibeam, descanner, detector, overlap=0, pre_calibrate=False): """ :param name: (str) Name of the region of acquisition (ROA). It is the name of the megafield (id) as stored on the external storage. :param coordinates: (float, float, float, float) xmin, ymin, xmax, ymax Bounding box coordinates of the ROA in [m]. The coordinates are in the sample carrier coordinate system, which corresponds to the component with role='stage'. :param roc_2: (FastEMROC) Corresponding region of calibration (ROC). Used for dark offset and digital gain calibration. :param roc_3: (FastEMROC) Corresponding region of calibration (ROC). Used for the final scanner rotation, final scanning amplitude and cell translation (cell stitching) calibration (field corrections). :param asm: (technolution.AcquisitionServer) The acquisition server module component. :param multibeam: (technolution.EBeamScanner) The multibeam scanner component of the acquisition server module. :param descanner: (technolution.MirrorDescanner) The mirror descanner component of the acquisition server module. :param detector: (technolution.MPPC) The detector object to be used for collecting the image data. :param overlap: (float), optional The amount of overlap required between single fields. An overlap of 0.2 means that two neighboring fields overlap by 20%. By default, the overlap is 0, this means there is no overlap and one field is exactly next to the neighboring field. :param pre_calibrate: (bool) If True run pre-calibrations before each ROA acquisition. """ self.name = model.StringVA(name) self.coordinates = model.TupleContinuous(coordinates, range=((-1, -1, -1, -1), (1, 1, 1, 1)), cls=(int, float), unit='m') self.roc_2 = model.VigilantAttribute(roc_2) self.roc_3 = model.VigilantAttribute(roc_3) self._asm = asm self._multibeam = multibeam self._descanner = descanner self._detector = detector # List of tuples(int, int) containing the position indices of each field to be acquired. # Automatically updated when the coordinates change. self.field_indices = [] self.overlap = overlap self.pre_calibrate = pre_calibrate self.coordinates.subscribe(self.on_coordinates, init=True)
def __init__(self, name, coordinates): """ :param name: (str) Name of the region of calibration (ROC). It is the name of the megafield (id) as stored on the external storage. :param coordinates: (float, float, float, float) left, top, right, bottom, Bounding box coordinates of the ROC in [m]. The coordinates are in the sample carrier coordinate system, which corresponds to the component with role='stage'. """ self.name = model.StringVA(name) self.coordinates = model.TupleContinuous(coordinates, range=((-1, -1, -1, -1), (1, 1, 1, 1)), cls=(int, float), unit='m') self.parameters = None # calibration object with all relevant parameters
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, microscope, main_app): super(TimelapsePlugin, self).__init__(microscope, main_app) # Can only be used with a microscope if not microscope: return else: # Check which stream the microscope supports main_data = self.main_app.main_data # TODO: also support backscatter detector if main_data.sed and main_data.ebeam: self.addMenu("Acquisition/Timelapse SEM...", self.start_sem) if main_data.ccd and main_data.light: self.addMenu("Acquisition/Timelapse Fluorescence...", self.start_fluo) self.period = model.FloatContinuous(10, (1e-3, 10000), unit="s") self.numberOfAcquisitions = model.IntContinuous(100, (2, 1000)) self.filename = model.StringVA("a.h5") self._stream = None
def __init__(self, microscope, main_app): super(CLAcqPlugin, self).__init__(microscope, main_app) # Can only be used with a microscope if not microscope: return else: # Check which stream the microscope supports main_data = self.main_app.main_data if not (main_data.ccd and main_data.ebeam): return self.exposureTime = main_data.ccd.exposureTime self.binning = main_data.ccd.binning self.xres = model.IntContinuous(10, (1, 1000), unit="px") self.yres = model.IntContinuous(10, (1, 1000), unit="px") self.stepsize = model.FloatVA(1e-6, unit="m") # Just to show self.filename = model.StringVA("a.tiff") self.xres.subscribe(self._update_stepsize) self.yres.subscribe(self._update_stepsize) self.addMenu("Acquisition/CL acquisition...", self.start)
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, parent, orig_tab_data): xrcfr_acq.__init__(self, parent) self.conf = get_acqui_conf() for n in presets: self.cmb_presets.Append(n) # TODO: record and reuse the preset used? self.cmb_presets.Select(0) self.filename = model.StringVA(create_filename(self.conf.last_path, self.conf.fn_ptn, self.conf.last_extension, self.conf.fn_count)) self.filename.subscribe(self._onFilename, init=True) # The name of the last file that got written to disk (used for auto viewing on close) self.last_saved_file = None # True when acquisition occurs self.acquiring = False # a ProgressiveFuture if the acquisition is going on self.acq_future = None self._acq_future_connector = None self._main_data_model = orig_tab_data.main # duplicate the interface, but with only one view self._tab_data_model = self.duplicate_tab_data_model(orig_tab_data) # Create a new settings controller for the acquisition dialog self._settings_controller = SecomSettingsController( self, self._tab_data_model, highlight_change=True # also adds a "Reset" context menu ) orig_view = orig_tab_data.focussedView.value self._view = self._tab_data_model.focussedView.value self._hidden_view = StreamView("Plugin View Hidden") self.streambar_controller = StreamBarController(self._tab_data_model, self.pnl_secom_streams, static=True, ignore_view=True) # The streams currently displayed are the one visible self.add_all_streams() # The list of streams ready for acquisition (just used as a cache) self._acq_streams = {} # FIXME: pass the fold_panels # Compute the preset values for each preset self._preset_values = {} # dict string -> dict (SettingEntries -> value) self._orig_entries = get_global_settings_entries(self._settings_controller) for sc in self.streambar_controller.stream_controllers: self._orig_entries += get_local_settings_entries(sc) self._orig_settings = preset_as_is(self._orig_entries) # to detect changes for n, preset in presets.items(): self._preset_values[n] = preset(self._orig_entries) # Presets which have been confirmed on the hardware self._presets_confirmed = set() # (string) self.start_listening_to_va() # If it could be possible to do fine alignment, allow the user to choose if self._can_fine_align(self._tab_data_model.streams.value): self.chkbox_fine_align.Show() # Set to True to make it the default, but will be automatically # disabled later if the current visible streams don't allow it. self.chkbox_fine_align.Value = True for s in self._tab_data_model.streams.value: if isinstance(s, EMStream): em_det = s.detector em_emt = s.emitter elif isinstance(s, OpticalStream) and not isinstance(s, ScannedFluoStream): opt_det = s.detector self._ovrl_stream = stream.OverlayStream("Fine alignment", opt_det, em_emt, em_det, opm=self._main_data_model.opm) self._ovrl_stream.dwellTime.value = self._main_data_model.fineAlignDwellTime.value else: self.chkbox_fine_align.Show(False) self.chkbox_fine_align.Value = False self._prev_fine_align = self.chkbox_fine_align.Value # make sure the view displays the same thing as the one we are # duplicating self._view.view_pos.value = orig_view.view_pos.value self._view.mpp.value = orig_view.mpp.value self._view.merge_ratio.value = orig_view.merge_ratio.value # attach the view to the viewport self.pnl_view_acq.canvas.fit_view_to_next_image = False self.pnl_view_acq.setView(self._view, self._tab_data_model) # The TOOL_ROA is not present because we don't allow the user to change # the ROA), so we need to explicitly request the canvas to show the ROA. if hasattr(self._tab_data_model, "roa") and self._tab_data_model.roa is not None: cnvs = self.pnl_view_acq.canvas self.roa_overlay = RepetitionSelectOverlay(cnvs, self._tab_data_model.roa, self._tab_data_model.fovComp) cnvs.add_world_overlay(self.roa_overlay) self.Bind(wx.EVT_CHAR_HOOK, self.on_key) self.btn_cancel.Bind(wx.EVT_BUTTON, self.on_close) self.btn_change_file.Bind(wx.EVT_BUTTON, self.on_change_file) self.btn_secom_acquire.Bind(wx.EVT_BUTTON, self.on_acquire) self.cmb_presets.Bind(wx.EVT_COMBOBOX, self.on_preset) self.Bind(wx.EVT_CLOSE, self.on_close) # on_streams_changed is compatible because it doesn't use the args self.chkbox_fine_align.Bind(wx.EVT_CHECKBOX, self.on_streams_changed) self.on_preset(None) # will force setting the current preset # To update the estimated time when streams are removed/added self._view.stream_tree.flat.subscribe(self.on_streams_changed) self._hidden_view.stream_tree.flat.subscribe(self.on_streams_changed)
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)