Exemple #1
0
    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
Exemple #2
0
    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()
Exemple #3
0
    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()
Exemple #4
0
    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()
Exemple #5
0
    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
Exemple #6
0
    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()
Exemple #7
0
    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()
Exemple #8
0
    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)
Exemple #9
0
    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()
Exemple #10
0
    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()
Exemple #11
0
    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()
Exemple #12
0
    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