예제 #1
0
    def __init__(self, name, detector, dataflow, emitter, sem_stream, **kwargs):
        """
        sem_stream (SEMStream): an SEM stream with the same emitter
        """
        super(MomentOfInertiaLiveStream, self).__init__(name, detector, dataflow, emitter, **kwargs)
        # Initialise to some typical value: small so that it's fast
        self.repetition.value = (9, 9)

        # Fuzzing should not be needed
        del self.fuzzing

        # B/C is fixed to min/max, and histogram is pretty much useless
        del self.auto_bc
        del self.auto_bc_outliers
        del self.histogram

        # Region of interest as left, top, right, bottom (in ratio from the
        # whole area of the emitter => between 0 and 1) that defines the region
        # to be acquired for the MoI compution.
        # This is expected to be centered to the lens pole position.
        self.detROI = model.TupleContinuous((0, 0, 1, 1),
                                         range=((0, 0, 0, 0), (1, 1, 1, 1)),
                                         cls=(int, long, float), setter=self._setDetROI)

        # The background data (typically, an acquisition without ebeam).
        # It is subtracted from the acquisition data.
        # If set to None, baseline is used
        self.background = model.VigilantAttribute(None, setter=self._setBackground)

        # Future of the acquisition
        self._acq_stream = MomentOfInertiaMDStream("MoI acq", sem_stream, self)
        self._acquire_f = None
예제 #2
0
class MomentOfInertiaLiveStream(CCDSettingsStream):
    """
    Special stream to acquire AR view and display moment of inertia live.
    Also provides spot size information.
    Needs a SEMStream.
    Note: internally it uses MomentOfInertiaSyncStream to actually acquire data.
    """

    def __init__(self, name, detector, dataflow, emitter, sem_stream, **kwargs):
        """
        sem_stream (SEMStream): an SEM stream with the same emitter
        """
        super(MomentOfInertiaLiveStream, self).__init__(name, detector, dataflow, emitter, **kwargs)
        # Initialise to some typical value: small so that it's fast
        self.repetition.value = (9, 9)

        # Fuzzing should not be needed
        del self.fuzzing

        # B/C is fixed to min/max, and histogram is pretty much useless
        del self.auto_bc
        del self.auto_bc_outliers
        del self.histogram

        # Region of interest as left, top, right, bottom (in ratio from the
        # whole area of the emitter => between 0 and 1) that defines the region
        # to be acquired for the MoI compution.
        # This is expected to be centered to the lens pole position.
        self.detROI = model.TupleContinuous((0, 0, 1, 1),
                                         range=((0, 0, 0, 0), (1, 1, 1, 1)),
                                         cls=(int, long, float), setter=self._setDetROI)

        # The background data (typically, an acquisition without ebeam).
        # It is subtracted from the acquisition data.
        # If set to None, baseline is used
        self.background = model.VigilantAttribute(None, setter=self._setBackground)

        # Future of the acquisition
        self._acq_stream = MomentOfInertiaMDStream("MoI acq", sem_stream, self)
        self._acquire_f = None

    def _setDetROI(self, roi):
        """
        Setter for the .detROI VA
        Synchronises the detROI VA with the VA of the acquisition stream
        """
        self._acq_stream.detROI.value = roi
        return roi

    def _setBackground(self, data):
        """
        Setter for the .background VA
        Synchronises the background VA with the VA of the acquisition stream
        """
        self._acq_stream.background.value = data
        return data

    def _projectMoI2RGB(self, data, valid):
        """
        Project a 2D spatial DataArray into a RGB representation
        data (DataArray): 2D DataArray
        valid (numpy.ndarray of bool)
        return (DataArray): 3D DataArray
        """
        # Note: NaN values will become 0 (and 255 after inversion)
        rgbim = img.DataArray2RGB(data)
        # Inverse the contrast, because the smallest the MoI, the brighter the
        # pixel should be.
        rgbim = 255 - rgbim

        # Make non valid/clipping pixels reddish.
        for (x, y), v in numpy.ndenumerate(valid):
            if not v:
                if math.isnan(data[x, y]):  # We don't want it too bright
                    rgbim[x, y] = [64, 0, 0]
                else:
                    val = rgbim[x, y, 0]
                    rgbim[x, y] = [min(val + 64, 255), val // 4, val // 4]
        rgbim.flags.writeable = False
        md = self._find_metadata(data.metadata)
        md[model.MD_DIMS] = "YXC"  # RGB format
        return model.DataArray(rgbim, md)

    def _updateImage(self):
        if self.raw:
            moi, valid = self.raw[1:3]  # 2nd and 3rd data are useful for us
            self.image.value = self._projectMoI2RGB(moi, valid)

    def _on_acq_done(self, future):
        # Pretty much the same as _onNewData(), but also relaunch an acquisition
        try:
            logging.debug("MoI acquisition finished")
            try:
                self.raw = future.result()  # sem, moi, valid, spot int., raw CCD center
                if not future.cancelled():
                    self._shouldUpdateImage()
            except CancelledError:
                pass
        except Exception:
            logging.exception("Failed to acquire data")

        # start the next acquisition
        if self.is_active.value:
            self._acquire_f = self._acq_stream.acquire()
            self._acquire_f.add_done_callback(self._on_acq_done)

    def _onActive(self, active):
        """ Called when the Stream is activated or deactivated by setting the
        is_active attribute
        """
        if active:
            # Convert the .acquire() future into a live acquisition
            if not self.should_update.value:
                logging.warning("Trying to activate stream while it's not "
                                "supposed to update")
            self._bg_image = self.background.value
            # approx. the index of the center image

            self._acquire_f = self._acq_stream.acquire()
            self._acquire_f.add_done_callback(self._on_acq_done)
        else:
            self._acquire_f.cancel()

    def getRawValue(self, pos):
        """
        Return the raw value at the given position and the maxima
        pos (int, int): position on the array
        return:
            (0<float or None): raw value of the moment of inertia
            (None or tuple of floats): min/max raw values
        raises:
             IndexError if pos is incorrect
        """
        raw = self.raw
        if len(raw) >= 3:
            data = raw[1].view(numpy.ndarray)  # To ensure we get floats
            return data[pos], (numpy.nanmin(data), numpy.nanmax(data))
        else:
            # Nothing yet
            return None, None

    # TODO: take as argument the pixel position?
    def getImageCCD(self):
        """
        Return the CCD image at the center
        return (DataArray or None): raw CCD data
        """
        raw = self.raw
        if len(raw) < 5:
            # Nothing yet
            return None

        data = raw[4]
        # TODO: find spot center and crop around it? Also apply background subtraction?
        rgbim = img.DataArray2RGB(data)
        rgbim.flags.writeable = False
        md = self._find_metadata(data.metadata)
        md[model.MD_DIMS] = "YXC"  # RGB format
        return model.DataArray(rgbim, md)

    # TODO: take as argument the pixel position?
    def getSpotIntensity(self):
        """
        return (0<=float<=1): spot intensity
        """
        raw = self.raw
        if len(raw) >= 4:
            return raw[3][()]
        else:
            # Nothing yet
            return None