def _calc_acq_times(self): """ Calculate exposure times for different elements of the acquisition. return (3 float): in s """ dt_survey = 0 dt_cl = 0 dt_drift = 0 if self._survey_s: dt_survey = self._survey_s.estimateAcquisitionTime() if self._cl_int_s: dt_cl = self._cl_int_s.estimateAcquisitionTime() # For each CL filter acquisition, the drift correction will run once # (*in addition* to the standard in-frame drift correction) dc = self._acqui_tab.driftCorrector if dc.roi.value != UNDEFINED_ROI: drift_est = drift.AnchoredEstimator(self.ebeam, self.sed, dc.roi.value, dc.dwellTime.value) dt_drift = drift_est.estimateAcquisitionTime() + 0.1 return dt_survey, dt_cl, dt_drift
def series_start(self): if self.roi.value == UNDEFINED_ROI: raise ValueError("AnchorDriftCorrector.roi is not defined") self._dc_estimator = drift.AnchoredEstimator(self._scanner, self._detector, self.roi.value, self.dwellTime.value) # First acquisition of anchor area self._dc_estimator.acquire()
def series_start(self): """ Creates the drift estimator object for the acquisition series and runs a first acquisition of the anchor region. """ if self.roi.value == UNDEFINED_ROI: raise ValueError("AnchorDriftCorrector.roi is not defined") self._dc_estimator = drift.AnchoredEstimator(self._scanner, self._detector, self.roi.value, self.dwellTime.value) # First acquisition of anchor area self._dc_estimator.acquire()
def estimateAcquisitionTime(self, dt, shape): """ Compute an approximation of how long the leech will increase the time of an acquisition. return (0<float): time in s added to the whole acquisition """ if self.roi.value == UNDEFINED_ROI: return 0 # number of times the anchor will be acquired * anchor acquisition time dce = drift.AnchoredEstimator(self._scanner, self._detector, self.roi.value, self.dwellTime.value) period = dce.estimateCorrectionPeriod(self.period.value, dt, shape[-2:]) npixels = numpy.prod(shape) n_anchor = 1 + npixels // next(period) return n_anchor * dce.estimateAcquisitionTime()
def estimateAcquisitionTime(self): """ Estimate the time it will take for the measurement. The number of pixels still has to be defined in the stream part """ xres, yres = self.get_scan_res() npos = xres * yres dt = self.dwellTime.value * npos * 1.1 # logic that only adds acquisition time for DC if a DC region is defined if self.dcRegion.value != UNDEFINED_ROI: dc = drift.AnchoredEstimator(self._emitter, self._sed, self.dcRegion.value, self.dcDwellTime.value) dctime = dc.estimateAcquisitionTime() nDC = self.nDC.value # time for spatial drift correction, for now we just assume that spatial drift correction is done every pixel but we could include actual number of scanned pixelsv dt += (npos * nDC + 1) * (dctime + 0.1) return dt
def estimateAcquisitionTime(self, acq_t, shape): """ Compute an approximation of how long the leech will increase the time of an acquisition. :param acq_t: (float) Time spend for acquiring one data at the fastest dimension. :param shape: (tuple of int): Dimensions are sorted from slowest to fasted axis for acquisition. It always includes the number of pixel positions to be acquired (y, x). Other dimensions can be multiple images that are acquired per pixel (ebeam) position. :returns: (0<float) Time in s added to the whole acquisition. """ if self.roi.value == UNDEFINED_ROI: return 0 dce = drift.AnchoredEstimator(self._scanner, self._detector, self.roi.value, self.dwellTime.value) # only pass the two fastest axes of the acquisition to retrieve the period when a leech should be run again period = dce.estimateCorrectionPeriod(self.period.value, acq_t, shape[-2:]) nimages = numpy.prod(shape) n_anchor = 1 + nimages // next(period) # number of times the anchor will be acquired * anchor acquisition time return n_anchor * dce.estimateAcquisitionTime()
def acquire(self, dlg): # Stop the spot stream and any other stream playing to not interfere with the acquisition self._pause_streams() # We use the acquisition CL intensity stream, so there is a concurrent # SEM acquisition (just the survey). The drift correction is run both # during the acquisition, and in-between each acquisition. The drift # between each acquisition is corrected by updating the metadata. So # it's some kind of post-processing compensation. The advantage is that # it doesn't affect the data, and if the entire field of view is imaged, # it still works properly, but when opening in another software (eg, # ImageJ), that compensation will not be applied automatically). # Alternatively, the images could be cropped to just the region which is # common for all the acquisitions, but there might then be data loss. # Note: The compensation could also be done by updating the ROI of the # CL stream. However, in the most common case, the user will acquire the # entire area, so drift compensation cannot be applied. We could also # use SEM concurrent stream and measure drift afterwards but that # doubles the dwell time). dt_survey, dt_clint, dt_drift = self._calc_acq_times() das = [] fn = self.filename.value exporter = dataio.find_fittest_converter(fn) dur = self.expectedDuration.value end = time.time() + dur ft = model.ProgressiveFuture(end=end) ft.task_canceller = lambda l: True # To allow cancelling while it's running ft.set_running_or_notify_cancel() # Indicate the work is starting now dlg.showProgress(ft) # TODO: if the user cancels the acquisition, the GUI shows as if it's # been immediately stopped, but the code only detects it at the end of # the CL acquisition. => Need to listen to the future, and immediately # cancel the current acquisition. try: # acquisition of SEM survey if self._survey_s: d, e = acq.acquire([self._survey_s]).result() das.extend(d) dur -= dt_survey ft.set_progress(end=time.time() + dur) # Extra drift correction between each filter dc_roi = self._acqui_tab.driftCorrector.roi.value dc_dt = self._acqui_tab.driftCorrector.dwellTime.value # drift correction vector tot_dc_vect = (0, 0) if dc_roi != UNDEFINED_ROI: drift_est = drift.AnchoredEstimator(self.ebeam, self.sed, dc_roi, dc_dt) drift_est.acquire() dur -= dt_drift ft.set_progress(end=time.time() + dur) else: drift_est = None # Loop over the filters, for now it's fixed to 3 but this could be flexible for fb, co in zip(self._filters, self._colours): logging.info("Moving to band %s with component %s", fb.value, self.filterwheel.name) self.filterwheel.moveAbs({"band": fb.value}).result() ft.set_progress(end=time.time() + dur) # acquire CL stream d, e = acq.acquire([self._cl_int_s]).result() if ft.cancelled(): return dur -= dt_clint ft.set_progress(end=time.time() + dur) if drift_est: drift_est.acquire() dc_vect = drift_est.estimate() pxs = self.ebeam.pixelSize.value tot_dc_vect = (tot_dc_vect[0] + dc_vect[0] * pxs[0], tot_dc_vect[1] - dc_vect[1] * pxs[1]) # Y is inverted in physical coordinates dur -= dt_drift ft.set_progress(end=time.time() + dur) # Convert the CL intensity stream into a "fluo" stream so that it's nicely displayed (in colour) in the viewer for da in d: # Update the center position based on drift pos = da.metadata[model.MD_POS] logging.debug("Correcting position for drift by %s m", tot_dc_vect) pos = tuple(p + dc for p, dc in zip(pos, tot_dc_vect)) da.metadata[model.MD_POS] = pos if model.MD_OUT_WL not in da.metadata: # check it's not the SEM concurrent stream continue # Force the colour, which forces it to be a FluoStream when # opening it in the analysis tab, for nice colour merging. da.metadata[model.MD_USER_TINT] = co das.extend(d) ft.set_result(None) # Indicate it's over except CancelledError: pass finally: # Make sure we don't hold reference to the streams forever self._survey_s = None self._cl_int_s = None if not ft.cancelled() and das: if e: logging.warning("RGB CL intensity acquisition partially failed: %s", e) logging.debug("Will save data to %s", fn) # logging.debug("Going to export data: %s", das) exporter.export(fn, das) self.showAcquisition(fn) dlg.Close()
def acquire_arcube(self, shape, spot, filename, dperiod=None, anchor=None): """ shape (int, int) spot (float, float) filename (str) dperiod (0<float): drift correction period anchor (4* 0<=float<=1): anchor region for drift correction """ # Set up the drift correction (using a 10µs dwell time for the anchor) if anchor: de = drift.AnchoredEstimator(self.escan, self.sed, anchor, 10e-6) de.acquire() # original anchor region # Estimate the number of pixels the drift period corresponds to px_time = ( self.spect.exposureTime.value + # exposure time numpy.prod(self.spect.resolution.value) / self.spect.readoutRate.value + # readout time 0.1) # overhead (eg, pinhole movement) px_iter = de.estimateCorrectionPeriod(dperiod, px_time, shape) next_dc = px_iter.next() # Set the E-beam in spot mode (order matters) self.escan.scale.value = (1, 1) self.escan.resolution.value = (1, 1) self.escan.dwellTime.value = 0.1 # s, anything not too short/long is fine # start the e-beam "scanning" self.sed.data.subscribe(self.discard_sem) # start the CCD acquisition, blocked on softwareTrigger self.spect.data.synchronizedOn(self.spect.softwareTrigger) self.spect.data.subscribe(self.on_spectrum) spec_data = [] n = 0 for i in numpy.ndindex(shape[::-1]): # scan along X fast, then Y logging.info("Going to acquire AR point %s", i) # TODO: replace next line by code waiting for the pinhole actuator # to be finished moving. raw_input("Press enter to start next spectrum acquisition...") spec = self.acquire_spec(spot) # TODO: replace next line by code letting know the pinhole actuator # that it should go to next point. print("Spectrum for point %s just acquired" % (i, )) spec_data.append(spec) if anchor: # Time to do drift-correction? n += 1 if n >= next_dc: de.acquire() # take a new d = de.estimate() self.drift = (self.drift[0] + d[0], self.drift[1] + d[1]) logging.info("Drift estimated to %s", self.drift) n = 0 next_dc = px_iter.next() # Stop all acquisition self.spect.data.unsubscribe(self.on_spectrum) self.spect.data.synchronizedOn(None) self.sed.data.unsubscribe(self.discard_sem) data = self.assemble_cube(shape, spec_data) # save the file exporter = dataio.find_fittest_exporter(filename) exporter.export(filename, data)
def _runAcquisition(self, future): self._detector.pixelDuration.value = self.pixelDuration.value logging.debug("Syncoffset used %s", self.syncOffset.value) logging.debug("SyncDiv used %s", self.syncDiv.value) # number of drift corrections per pixel nDC = self.nDC.value # semfov, physwidth = self._get_sem_fov() #xyps, stepsize = self._calc_xy_pos() xres, yres = self.get_scan_res() xyps = self.calc_xy_pos(self.roi.value, self.stepsize.value) logging.debug("Will scan on X/Y positions %s", xyps) #phys_rect = convert_roi_ratio_to_phys(escan,roi) measurement_n = 0 cordata = [] sedata = [] NPOS = len(xyps) # = xres * yres self._save_hw_settings() # a list (instead of a tuple) for the summation to work on each element independently tot_dc_vect = [0, 0] #check whether a drift region is defined if self.dcRegion.value != UNDEFINED_ROI: drift_est = drift.AnchoredEstimator(self._emitter, self._sed, self.dcRegion.value, self.dcDwellTime.value) drift_est.acquire() else: drift_est = None try: if drift_est: self._start_spot(nDC) # re-adjust dwell time for number of drift corrections self._detector.dwellTime.value = self.dwellTime.value / nDC self._emitter.dwellTime.value = self.dwellTime.value / nDC for x, y in xyps: sedatapix = [] cordatapix = [] for ll in range(self.nDC.value): # add total drift vector at this point xc = x - tot_dc_vect[0] yc = y - tot_dc_vect[1] # check if drift correction leads to an x,y position outside of scan region cx, cy = self._emitter.translation.clip((xc, yc)) if (cx, cy) != (xc, yc): logging.error( "Drift of %s px caused acquisition region out " "of bounds: needed to scan spot at %s.", tot_dc_vect, (xc, yc)) xc, yc = (cx, cy) xm, ym = self._convert_xy_pos_to_m(xc, yc) logging.info( "Acquiring scan number %d at position (%g, %g), with drift correction of %s", ll + 1, xm, ym, tot_dc_vect) startt = time.time() cordat, sedat = self._acquire_correlator( xc, yc, self.dwellTime.value / nDC, future) endt = time.time() logging.debug("Took %g s (expected = %g s)", endt - startt, self.dwellTime.value / nDC) cordatapix.append(cordat) sedatapix.append(sedat) logging.debug("Memory used = %d bytes", udriver.readMemoryUsage()) drift_est.acquire() # drift correction vectors dc_vect = drift_est.estimate() tot_dc_vect[0] += dc_vect[0] tot_dc_vect[1] += dc_vect[1] measurement_n += 1 # TODO: update the future progress logging.info("Acquired %d out of %d pixels", measurement_n, NPOS) # Perform addition of measurements here which keeps other # acquisitions the same and reduces memory required. cordatam = numpy.sum(cordatapix, 0, dtype=numpy.float64) # checks whether datavalue exceeds data-type range. # Note: this works for integers only. For floats there is a separate numpy function idt = numpy.iinfo(cordatapix[0].dtype) # we can choose different things here. For now we just force to clip the signal cordatam = numpy.clip(cordatam, idt.min, idt.max) # convert back to right datatype and (re)add metadata cordatam = model.DataArray( cordatam.astype(cordatapix[0].dtype), cordatapix[0].metadata) cordata.append(cordatam) # For SE data just use mean because absolute scale is not relevant sedatam = numpy.mean(sedatapix).astype(sedatapix[0].dtype) # The brackets are required to give enough dimensions to make the rest happy sedatam = model.DataArray([[[[sedatam]]]], sedatapix[0].metadata) sedata.append(sedatam) else: self._start_spot(1) for x, y in xyps: self._detector.dwellTime.value = self.dwellTime.value xm, ym = self._convert_xy_pos_to_m(x, y) logging.info("Acquiring at position (%g, %g)", xm, ym) startt = time.time() # dwelltime is used as input for the acquisition because it is different for with drift and without cordat, sedat = self._acquire_correlator( x, y, self.dwellTime.value, future) endt = time.time() logging.debug("Took %g s (expected = %g s)", endt - startt, self.dwellTime.value) cordata.append(cordat) sedata.append(sedat) logging.debug("Memory used = %d bytes", udriver.readMemoryUsage()) # number of scans that have been done. Could be printed to show progress measurement_n += 1 # TODO: update the future progress logging.info("Acquired %d out of %d pixels", measurement_n, NPOS) self._stop_spot() stepsize = (self.stepsize.value, self.stepsize.value) cordata[0].metadata[model.MD_POS] = sedata[0].metadata[ model.MD_POS] full_cordata = self._assemble_correlator_data( cordata, (xres, yres), self.roi.value, stepsize) full_sedata = self._assemble_sed_data(sedata, (xres, yres), self.roi.value, stepsize) if future._acq_state == CANCELLED: raise CancelledError() das = [full_cordata, full_sedata] if drift_est: das.append(self._assembleAnchorData(drift_est.raw)) return das except CancelledError: logging.info("Time correlator stream cancelled") with future._acq_lock: self._acq_state = FINISHED raise # Just don't log the exception except Exception: logging.exception("Failure during Correlator acquisition") raise finally: logging.debug("TC acquisition finished") # Make sure all detectors are stopped self._stop_spot() self._detector.data.unsubscribe(self._receive_tc_data) future._acq_done.set() self._resume_hw_settings()
def _runAcquisition(self, future): # number of drift corrections per pixel nDC = self.nDC.value # Initialize spectrograph CENTERWL = self.centerWavelength.value SLIT_WIDTH = self.slitWidth.value # move to appropriate center wavelength self._sgr.moveAbs({"wavelength": CENTERWL}).result() # set slit width self._sgr.moveAbs({"slit-in": SLIT_WIDTH}).result() dt = self.dwellTime.value self._emitter.dwellTime.value = dt #exposure time and dwell time should be the same in this case bins = (self.binninghorz.value, self.binningvert.value) self._detector.binning.value = bins specresx = self._detector.shape[0] // bins[0] specresy = self._detector.shape[1] // bins[1] self._detector.resolution.value = (specresx, specresy) # semfov, physwidth = self._get_sem_fov() #xyps, stepsize = self._calc_xy_pos() xres, yres = self.get_scan_res() xyps = self.calc_xy_pos(self.roi.value, self.stepsize.value) logging.debug("Will scan on X/Y positions %s", xyps) #phys_rect = convert_roi_ratio_to_phys(escan,roi) measurement_n = 0 ARdata = [] sedata = [] NPOS = len(xyps) # = xres * yres self._save_hw_settings() # drift correction vectors dc_vect = (0, 0) # list instead of tuple, to allow changing just one item at a time tot_dc_vect = [0, 0] if self.dcRegion.value != UNDEFINED_ROI: drift_est = drift.AnchoredEstimator(self._emitter, self._sed, self.dcRegion.value, self.dcDwellTime.value) drift_est.acquire() else: drift_est = None try: if drift_est: self._start_spot(nDC) # re-adjust dwell time for number of drift corrections self._detector.exposureTime.value = dt / nDC self._emitter.dwellTime.value = dt / nDC for x, y in xyps: sedatapix = [] sedatam = [] ARdatapix = [] ARdatam = [] for ll in range(self.nDC.value): # add total drift vector at this point xc = x - tot_dc_vect[0] yc = y - tot_dc_vect[1] # check if drift correction leads to an x,y position outside of scan region cx, cy = self._emitter.translation.clip((xc, yc)) if (cx, cy) != (xc, yc): logging.error( "Drift of %s px caused acquisition region out " "of bounds: needed to scan spot at %s.", tot_dc_vect, (xc, yc)) xc, yc = (cx, cy) xm, ym = self._convert_xy_pos_to_m(xc, yc) logging.info( "Acquiring scan number %d at position (%g, %g), with drift correction of %s", ll + 1, xm, ym, tot_dc_vect) startt = time.time() ARdat, sedat = self._acquire_ARspec( x, y, dt / nDC, future) endt = time.time() logging.debug("Took %g s (expected = %g s)", endt - startt, dt / nDC) ARdatapix.append(ARdat) sedatapix.append(sedat) logging.debug("Memory used = %d bytes", udriver.readMemoryUsage()) drift_est.acquire() dc_vect = drift_est.estimate() tot_dc_vect[0] += dc_vect[0] tot_dc_vect[1] += dc_vect[1] measurement_n += 1 # TODO: update the future progress logging.info("Acquired %d out of %d pixels", measurement_n, NPOS) # Perform addition of measurements here which keeps other # acquisitions the same and reduces memory required. We use 32 bits in this case as the data is 16 bits. ARdatam = numpy.sum(ARdatapix, 0, dtype=numpy.float32) # checks whether datavalue exceeds data-type range. # Note: this works for integers only. For floats there is a separate numpy function idt = numpy.iinfo(ARdatapix[0].dtype) # we can choose different things here. For now we just force to clip the signal ARdatam = numpy.clip(ARdatam, idt.min, idt.max) # convert back to right datatype and (re)add metadata ARdatam = model.DataArray( ARdatam.astype(ARdatapix[0].dtype), ARdatapix[0].metadata) ARdata.append(ARdatam) # For SE data just use mean because absolute scale is not relevant sedatam = numpy.mean(sedatapix).astype(sedatapix[0].dtype) # The brackets are required to give enough dimensions to make the rest happy sedatam = model.DataArray([[[[sedatam]]]], sedatapix[0].metadata) sedata.append(sedatam) else: self._start_spot(1) for x, y in xyps: self._detector.exposureTime.value = dt xm, ym = self._convert_xy_pos_to_m(x, y) logging.info("Acquiring at position (%g, %g)", xm, ym) startt = time.time() # dwelltime is used as input for the acquisition because it is different for with drift and without ARdat, sedat = self._acquire_ARspec( x, y, self.dwellTime.value, future) endt = time.time() logging.debug("Took %g s (expected = %g s)", endt - startt, self.dwellTime.value) ARdata.append(ARdat) sedata.append(sedat) logging.debug("Memory used = %d bytes", udriver.readMemoryUsage()) # number of scans that have been done. Could be printed to show progress measurement_n += 1 # TODO: update the future progress logging.info("Acquired %d out of %d pixels", measurement_n, NPOS) self._stop_spot() stepsize = (self.stepsize.value, self.stepsize.value) ARdata[0].metadata[model.MD_POS] = sedata[0].metadata[model.MD_POS] full_ARdata = self._assemble_ARspectral_data( ARdata, (xres, yres), self.roi.value, stepsize, bins, specresx) full_sedata = self._assemble_sed_data(sedata, (xres, yres), self.roi.value, stepsize) if future._acq_state == CANCELLED: raise CancelledError() das = [full_ARdata, full_sedata] if drift_est: das.append(self._assembleAnchorData(drift_est.raw)) return das except CancelledError: logging.info("AR spectral stream cancelled") self._stop_spot() with future._acq_lock: self._acq_state = FINISHED raise # Just don't log the exception except Exception: logging.exception("Failure during AR spectral acquisition") raise finally: logging.debug("AR spectral acquisition finished") self._sed.data.unsubscribe(self._receive_sem_data) future._acq_done.set() self._resume_hw_settings()
def acquire(self, dlg): # Stop the spot stream and any other stream playing to not interfere with the acquisition self._pause_streams() # We use the acquisition CL intensity stream, so there is a concurrent # SEM acquisition (in addition to the survey). The drift correction is run both # during the acquisition, and in-between each acquisition. The drift # between each acquisition is corrected by updating the metadata. So # it's some kind of post-processing compensation. The advantage is that # it doesn't affect the data, and if the entire field of view is imaged, # it still works properly, but when opening in another software (eg, # ImageJ), that compensation will not be applied automatically). # Alternatively, the images could be cropped to just the region which is # common for all the acquisitions, but there might then be data loss. # Note: The compensation could also be done by updating the ROI of the # CL stream. However, in the most common case, the user will acquire the # entire area, so drift compensation cannot be applied. We could also # use SEM concurrent stream and measure drift afterwards but that # doubles the dwell time). dt_survey, dt_clint, dt_drift = self._calc_acq_times() cl_set_s = next(s for s in self._cl_int_s.streams if hasattr(s, "axisFilter")) das = [] fn = self.filename.value exporter = dataio.find_fittest_converter(fn) # Prepare the Future to represent the acquisition progress, and cancel dur = self.expectedDuration.value end = time.time() + dur ft = model.ProgressiveFuture(end=end) # Allow to cancel by cancelling also the sub-task def canceller(future): # To be absolutely correct, there should be a lock, however, in # practice in the worse case the task will run a little longer before # stopping. if future._subf: logging.debug("Cancelling sub future %s", future._subf) return future._subf.cancel() ft._subf = None # sub-future corresponding to the task currently happening ft.task_canceller = canceller # To allow cancelling while it's running # Indicate the work is starting now ft.set_running_or_notify_cancel() dlg.showProgress(ft) try: # acquisition of SEM survey if self._survey_s: ft._subf = acqmng.acquire([self._survey_s], self.main_app.main_data.settings_obs) d, e = ft._subf.result() das.extend(d) if e: raise e if ft.cancelled(): raise CancelledError() dur -= dt_survey ft.set_progress(end=time.time() + dur) # Extra drift correction between each filter dc_roi = self._acqui_tab.driftCorrector.roi.value dc_dt = self._acqui_tab.driftCorrector.dwellTime.value # drift correction vector tot_dc_vect = (0, 0) if dc_roi != UNDEFINED_ROI: drift_est = drift.AnchoredEstimator(self.ebeam, self.sed, dc_roi, dc_dt) drift_est.acquire() dur -= dt_drift ft.set_progress(end=time.time() + dur) else: drift_est = None # Loop over the filters, for now it's fixed to 3 but this could be flexible for fb, co in zip(self._filters, self._colours): cl_set_s.axisFilter.value = fb.value logging.debug("Using band %s", fb.value) ft.set_progress(end=time.time() + dur) # acquire CL stream ft._subf = acqmng.acquire([self._cl_int_s], self.main_app.main_data.settings_obs) d, e = ft._subf.result() if e: raise e if ft.cancelled(): raise CancelledError() dur -= dt_clint ft.set_progress(end=time.time() + dur) if drift_est: drift_est.acquire() dc_vect = drift_est.estimate() pxs = self.ebeam.pixelSize.value tot_dc_vect = (tot_dc_vect[0] + dc_vect[0] * pxs[0], tot_dc_vect[1] - dc_vect[1] * pxs[1]) # Y is inverted in physical coordinates dur -= dt_drift ft.set_progress(end=time.time() + dur) # Convert the CL intensity stream into a "fluo" stream so that it's nicely displayed (in colour) in the viewer for da in d: # Update the center position based on drift pos = da.metadata[model.MD_POS] logging.debug("Correcting position for drift by %s m", tot_dc_vect) pos = tuple(p + dc for p, dc in zip(pos, tot_dc_vect)) da.metadata[model.MD_POS] = pos if model.MD_OUT_WL not in da.metadata: # check it's not the SEM concurrent stream continue # Force the colour, which forces it to be a FluoStream when # opening it in the analysis tab, for nice colour merging. da.metadata[model.MD_USER_TINT] = co das.extend(d) if ft.cancelled(): raise CancelledError() ft.set_result(None) # Indicate it's over except CancelledError as ex: logging.debug("Acquisition cancelled") return except Exception as ex: logging.exception("Failure during RGB CL acquisition") ft.set_exception(ex) # TODO: show the error in the plugin window return if ft.cancelled() or not das: return logging.debug("Will save data to %s", fn) exporter.export(fn, das) self.showAcquisition(fn) dlg.Close()
def acquire(self, fn): self.save_hw_settings() # it's not possible to keep in memory all the CCD images, so we save # them one by one in separate files (fn-ccd-XXXX.tiff) fn_base, fn_ext = os.path.splitext(fn) try: cycles = 1 anchor = self.anchor logging.info("Target ROI is [{:.3f},{:.3f},{:.3f},{:.3f}]".format( *self.roi)) rep = self.calc_rep(self.roi) # returns nr of SR pixels in x,y rep = self.check_gridpitch(rep, self.gridpitch) roi = self.update_roi(self.roi, rep) logging.info( "Updated ROI to [{:.3f},{:.3f},{:.3f},{:.3f}], in order to fit integer number of SR spots" .format(*roi)) spots = self.calc_xy_pos(roi, rep) # spots are defined shape = spots.shape[0:2] logging.info("Will scan %d x %d pixels.", shape[1], shape[0]) logging.info("Using %d x %d gridscans.", self.gridpitch[0], self.gridpitch[1]) dur = self.estimate_acq_time(shape) logging.info("Estimated acquisition time: %s", units.readable_time(round(dur))) # Let's go! logging.info("Acquiring full image 0") self.acquire_and_save_optical_survey(fn, "fullBefore") logging.info("Starting with bleaching a border around the ROI") sem_border = self.bleach_borders(roi) self.save_data(sem_border, "%s-SEM_bleached_border%s" % (fn_base, fn_ext)) ccd_roi = self.sem_roi_to_ccd(roi) ccd_roi = [ ccd_roi[0] - MARGIN_ACQ, ccd_roi[1] - MARGIN_ACQ, ccd_roi[2] + MARGIN_ACQ, ccd_roi[3] + MARGIN_ACQ ] ccd_roi_idx = ( slice(ccd_roi[1], ccd_roi[3] + 1), slice(ccd_roi[0], ccd_roi[2] + 1) ) # + 1 because we want to include the corners of the ROI self.configure_ccd(ccd_roi) self.configure_sem_for_tile() # start with the original fluorescence count (no bleaching) ccd_data = self.get_fluo_image() ccd_data_to_save = ccd_data[ccd_roi_idx] # Set up the drift correction (using the dwell time used for overview) if anchor: logging.info("Starting with pre-exposing the anchor region") de = drift.AnchoredEstimator(self.ebeam, self.sed, anchor, self.dtsem) for ii in range(13): de.acquire() de = drift.AnchoredEstimator(self.ebeam, self.sed, anchor, self.dtsem) px_iter = de.estimateCorrectionPeriod(self.dperiod, 1.0, self.gridpitch) de.acquire() # original anchor region self.save_data( de.raw[-1], "%s%02d-driftAnchor-%05d%s" % (fn_base, 0, 0, fn_ext)) self.driftlog = [(0, 0)] self.drifttime = [time.time()] next_dc = px_iter.next() for ii in range(cycles): # TODO: fix this super-arbitrary dwell time change if ii >= 1: self.ebeam.dwellTime.value = 2 * self.dt n = 0 sem_data = [] # save the fluorescence without bleaching (or end of previous cycle) self.save_data(ccd_data_to_save, "%s%02d-ccd-%05d%s" % (fn_base, ii, n, fn_ext)) for g in numpy.ndindex(self.gridpitch): n += 1 spotssubset = spots[g[0]::self.gridpitch[0], g[1]::self.gridpitch[1], :] subsetshape = spotssubset.shape[0:2] dur_frame = self.estimate_acq_time_gridscan(subsetshape) overhead_est = 1 * dur_frame + 0.1 self.ccd.exposureTime.value = dur_frame + overhead_est logging.info( "Setting exposuretime for spotsimage to: %s (includes %s overhead)", units.readable_time((dur_frame + overhead_est)), units.readable_time((overhead_est))) self.ccd_acq_complete.clear() self.ccd.data.subscribe(self.on_ccd_data) time.sleep(0.2) logging.info( "Bleaching pixels (%d,%d) + (%d,%d)*(0 -> %d, 0 -> %d)", g[1], g[0], self.gridpitch[1], self.gridpitch[0], subsetshape[1] - 1, subsetshape[0] - 1) for i in numpy.ndindex(subsetshape): bl_subset = self.bleach_spot(spotssubset[i].tolist()) sem_data.append(bl_subset) self.ccd_acq_complete.wait() if self.ccd_acq_complete.is_set(): logging.info("SEM bleaching took longer than expected") spotsimage_to_save = self._spotsimage[ccd_roi_idx] self.save_data( spotsimage_to_save, "%s%02d-spots-%05d%s" % (fn_base, ii, n, fn_ext)) ccd_data = self.get_fluo_image() # ccd_data_to_save = ccd_data ccd_data_to_save = ccd_data[ccd_roi_idx] self.save_data( ccd_data_to_save, "%s%02d-ccd-%05d%s" % (fn_base, ii, n, fn_ext)) if anchor: # Check whether the drift-correction needs to take place if n >= next_dc: de.acquire() # take a new d = de.estimate() self.drift = (self.drift[0] + d[0], self.drift[1] + d[1]) logging.info( "Drift estimated to {:.1f}, {:.1f} px".format( *self.drift)) next_dc = n + px_iter.next() self.save_data( de.raw[-1], "%s%02d-driftAnchor-%05d%s" % (fn_base, ii, n, fn_ext)) self.drifttime.append(time.time()) self.driftlog.append(self.drift) # Reconstruct the complete images logging.info("Reconstructing SEM image from tiles") sem_array = numpy.array( sem_data) # put everything in a big array sem_final = self.assemble_tiles(shape, sem_array, roi, self.gridpitch) sem_final.metadata[model.MD_DWELL_TIME] = self.dt sem_final.metadata[model.MD_DESCRIPTION] = "SEM" self.save_data( sem_final, "%s%02d_SEM_during_scan%s" % (fn_base, ii, fn_ext)) # acquire a full image again after bleaching logging.info("Acquiring full image 1") self.acquire_and_save_optical_survey(fn, "fullAfter") if anchor: de.acquire() # take a new d = de.estimate() self.drift = (self.drift[0] + d[0], self.drift[1] + d[1]) logging.info( "Drift estimated to {:.1f}, {:.1f} px".format(*self.drift)) self.save_data(de.raw[-1], "%s-driftAnchor-final%s" % (fn_base, fn_ext)) # take the SEM image again with specified dwell time and step size logging.info("Acquiring SEM survey") sem_survey_data = self.acquire_sem_survey(roi) sem_survey_data.metadata[model.MD_DESCRIPTION] = "SEM survey" self.save_data(sem_survey_data, "%s_SEM_survey%s" % (fn_base, fn_ext)) finally: self.resume_hw_settings() self.light.power.value = self.light.power.range[ 0] # makes sure light won't stay on