Example #1
0
    def stateRequest(self, request, timeout=TIMEOUT):
        """
        Helper method used to get or set camera state.

        Parameters
        ----------
        request : `string`
            Command to be passed to the camera
        timeout : `float`
            Time to wait for camera answer

        Returns
        -------
        `int`
        """

        self.socket.send(request)
        timer = Timer(timeout)

        r = b''
        while b'\n' not in r and timer.wait(self.socket):
            r += self.socket.recv(1)

        if timer.expired():
            raise RuntimeError('Camera is not answering')

        r = r.strip(b'\x00\n')

        return int(r, 0)
Example #2
0
    def stateRequest(self, request, timeout=TIMEOUT):
        """
        Helper method used to get or set camera state.

        Parameters
        ----------
        request : `string`
            Command to be passed to the camera
        timeout : `float`
            Time to wait for camera answer

        Returns
        -------
        `int`
        """

        self.socket.send(request)
        timer = Timer(timeout)

        r = b''
        while b'\n' not in r and timer.wait(self.socket):
            r += self.socket.recv(1)

        if timer.expired():
            raise RuntimeError('Camera is not answering')

        r = r.strip(b'\x00\n')

        return int(r, 0)
Example #3
0
    def __init__(self, mnemonic, address):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        mnemonic : `string`
            Camera mnemonic
        address : `tuple`
            Camera host server Internet address
        """
        super().__init__(mnemonic)
        self.socket = socket(AF_INET, SOCK_STREAM)
        self.socket.connect(address)
        self.counting = False
        self.timer = Timer(1)
        self.subScan = False
        self.subScanStep = 0

        # Force clearing busy and error flags. Both busy and error flags
        # May get stuck after exceptional conditions.
        try:
            state = self.waitWhile(self.STATE_MASK_BUSY, self.URGENT_TIMEOUT)
        except RuntimeError:
            self.socket.send(b'abort\n')

            try:
                self.setState(0, self.URGENT_TIMEOUT)
            except RuntimeError as e:
                self.socket.close()
                raise e from None
Example #4
0
    def __init__(self,
                 mnemonic,
                 address,
                 shutterType,
                 shutter=None,
                 shutterReadBack=None):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        mnemonic : `string`
            Camera mnemonic
        address : `tuple`
            Camera host server Internet address
        shutterType : `string`
            The type of software controlled shutter. The type can be "simple", "toggle"
            or "null". The null shutter completely disables software controlled shutter.
            The simple shutter is an EPICS PV that must be set to 0 to open the shutter
            and 1 to close the shutter. The toggle shutter uses two PVs, one that
            changes the shutter state whenever written to and another to read back the
            current shutter state.
        shutter :  `string`
            The shutter PV name. Only meaningful if the shutter type is not null.
        shutterReadBack : `string`
            The toggle shutter read back PV.
        """
        super().__init__(mnemonic)
        self.socket = socket(AF_INET, SOCK_STREAM)
        self.socket.connect(address)
        self.counting = False
        self.timer = Timer(1)
        self.subScan = False
        self.subScanStep = 0

        shutterName = mnemonic + '_shutter'

        if shutterType == 'toggle':
            self.shutter = ToggleShutter(shutterName, shutter, shutterReadback)
        elif shutterType == 'simple':
            self.shutter = SimpleShutter(shutterName, shutter)
        else:
            self.shutter = NullShutter(shutterName)

        self.shutter.close(wait=True)

        # Force clearing busy and error flags. Both busy and error flags
        # May get stuck after exceptional conditions.
        try:
            state = self.waitWhile(self.STATE_MASK_BUSY, self.URGENT_TIMEOUT)
        except RuntimeError:
            self.socket.send(b'abort\n')

            try:
                self.setState(0, self.URGENT_TIMEOUT)
            except RuntimeError as e:
                self.socket.close()
                raise e from None
Example #5
0
    def __init__(self,
                 mnemonic,
                 pv=None,
                 responseTimeout=15,
                 output="./out",
                 numPoints=1044,
                 mcas=False):
        """Constructor
        responseTimeout : how much time to wait qe65000 answer
        numPoints : how many points are collected each time
        """
        super().__init__(mnemonic, numPoints, output, 'ocean')
        self.acquireChanged = Event()
        self.acquiring = False

        try:
            # determines the start of counting
            self.pvStart = PV(pv + ":Acquire")
            # determines mode of Acquisition (Single,Continous, Dark Spectrum)
            self.pvAcquireMode = PV(pv + ":AcquisitionMode")

            # use darkcorrection
            self.pvDarkCorrection = PV(pv + ":ElectricalDark")

            # spectrum
            self.pvSpectrum = PV(pv + ":Spectra")
            self.pvSpectrumCorrected = PV(pv + ":DarkCorrectedSpectra")

            # set Acquire Time
            self.pvAcquireTime = PV(pv + ":SetIntegration")

            # integration Time
            self.pvTime = PV(pv + ":IntegrationTime:Value")

            # control the end of acquire process
            self.pvAcquire = PV(pv + ":Acquiring")
            self.pvAcquire.add_callback(self.statusChange)

            # acquisition mode
            self.pvAcMode = PV(pv + ":AcquisitionMode")
            # set to single mode
            self.pvAcMode.put("Single")

            # axis Spectra
            pvAxis = PV(pv + ":SpectraAxis")
            self.axis = pvAxis.get(as_numpy=True)[:self.numPoints]

            # regions of interest
            self.ROIS = []

            self.mcas = mcas

            self.responseTimeout = responseTimeout
            self.timer = Timer(self.responseTimeout)
        except TypeError:
            raise RuntimeError('PV not found')
        except ValueError:
            raise RuntimeError('Device is offline')
Example #6
0
    def __init__(self,
                 mnemonic,
                 numberOfChannels=4,
                 numberOfRois=32,
                 pv=None,
                 dxpType="mca",
                 responseTimeout=15,
                 output="./out",
                 numPoints=2048):
        """ Constructor
        responseTimeout : how much time to wait dxp answer
        imageDeep : how many points are collected each time
        """
        super().__init__(mnemonic, numPoints, output, dxpType)

        self.dxpType = dxpType
        self.acquireChanged = Event()
        self.acquiring = False

        # determines the start of counting
        self.pvDxpEraseStart = PV(pv + ":EraseStart.VAL")
        # determines mode of counting (Live Time, Real Time, ...)
        self.pvDxpPresetMode = PV(pv + ":PresetMode.VAL")

        self.pvDxpStop = PV(pv + ":StopAll.VAL")
        # store all channels
        self.pvDxpChannels = []
        # store ROIs
        self.pvDxpRois = []

        # store Acquire Time for each channel
        self.pvDxpAcquireTime = []
        self.pvDxpRealTime = []

        for c in range(0, numberOfChannels):
            # store each channel
            self.pvDxpChannels.append(PV(pv + ":" + dxpType + str(c + 1)))
            # for each channel store the PV for AcquireTime
            self.pvDxpAcquireTime.append(
                PV(pv + ":" + dxpType + "%d.PLTM" % (c + 1)))
            # real time
            self.pvDxpRealTime.append(
                PV(pv + ":" + dxpType + "%d.ERTM" % (c + 1)))
            self.pvDxpRois.append([])
            # storeing each ROI in your channel
            for r in range(0, numberOfRois):
                self.pvDxpRois[c].append(
                    PV(pv + ":" + dxpType + str(c + 1) + '.R' + str(r)))

        self.pvDxpAcquire = PV(pv + ":Acquiring")
        self.pvDxpAcquire.add_callback(self.statusChange)
        self.channels = numberOfChannels
        self.rois = numberOfRois

        self.responseTimeout = responseTimeout
        self.timer = Timer(self.responseTimeout)
Example #7
0
    def setCountTime(self, t):
        """
        Sets the image acquisition time.

        Parameters
        ----------
        t : `float`
            Acquisition time
        """
        self.timer = Timer(t)
Example #8
0
    def setCountTime(self, t):
        """
        Sets the image acquisition time.

        Parameters
        ----------
        t : `float`
            Acquisition time
        """
        self.pvAcquireTime.put(t, wait=True)
        self.timer = Timer(t + self.RESPONSE_TIMEOUT)
Example #9
0
    def setCountTime(self, time):
        """
        Method to set the count time of a scaler device.

        Parameters
        ----------
        time : `float`
            Count time to set to scaler device .

        Returns
        -------
        out : None
        """
        self.pvTime.put(time, wait=True)
        self.timer = Timer(time + self.responseTimeout)
Example #10
0
    def setCountTime(self, time):
        """
        Method to set the count time of a scaler device.

        Parameters
        ----------
        time : `float`
            Count time to set to scaler device .

        Returns
        -------
        out : None
        """
        for i in range(0, self.channels):
            self.pvDxpAcquireTime[i].put(time, wait=True)
        self.timer = Timer(time + self.responseTimeout)
Example #11
0
 def waitForIdle(self):
     """
     Blocks until the camera server is completely idle. This is required because
     the camera and the server are slow and certain operations, in particular,
     multiple sequential acquisitions would fail without waiting for some
     time. Acquiring two images and dezingering them only works with
     this method being called between the acquisitions.
     """
     # Minimum reliable wait time between two acquisitions was empirically found
     # to be 1.3 seconds. For wait times smaller than this, the camera server may
     # return it's ready even though it's not.
     timer = Timer(1.3)
     self.waitWhile(self.STATE_MASK_ACQUIRING | self.STATE_MASK_READING
                    | self.STATE_MASK_CORRECTING | self.STATE_MASK_WRITING
                    | self.STATE_MASK_DEZINGERING | self.STATE_MASK_BUSY)
     timer.wait()
Example #12
0
    def waitWhileOrUntil(self, condition, timeout=TIMEOUT, until=False):
        """
        Helper method that implements :meth:`waitWhile` and :meth:`waitUntil`
        """

        state = self.getState(timeout)
        timer = Timer(timeout)

        while until ^ bool(state & condition) and timer.check():
            state = self.getState(timeout)

            if state & self.STATE_MASK_ERROR:
                raise RuntimeError('Camera returned error: %x' % state)

        if timer.expired():
            raise RuntimeError('Camera got stuck condition: %x, state: %x' %
                               (condition, state))
Example #13
0
    def waitWhileOrUntil(self, condition, timeout=TIMEOUT, until=False):
        """
        Helper method that implements :meth:`waitWhile` and :meth:`waitUntil`
        """

        state = self.getState(timeout)
        timer = Timer(timeout)

        while until ^ bool(state & condition) and timer.check():
            state = self.getState(timeout)

            if state & self.STATE_MASK_ERROR:
                raise RuntimeError('Camera returned error: %x' % state)

        if timer.expired():
            raise RuntimeError('Camera got stuck condition: %x, state: %x' %
                               (condition, state))
Example #14
0
    def wait(self):
        """
        Blocks until the requested temperature is achieved.
        """
        if not self.pending:
            return

        # Waiting is done in two steps. First step waits until the IOC deasserts
        # the pending flag to indicate a complete operation. Second step waits util the
        # measured temperature reaches the requested temperature.
        ca.flush_io()
        self.done.wait()

        self.newTemperature.clear()
        timeout = Timer(7)
        while self.getValue() != self.requestedValue and timeout.check():
            self.newTemperature.wait(1)
            self.newTemperature.clear()
Example #15
0
    def setCountTime(self, t):
        """
        Sets the image acquisition time.

        Parameters
        ----------
        t : `float`
            Acquisition time
        """
        self.timer = Timer(t)
Example #16
0
    def __init__(self, mnemonic, address, shutterType, shutter=None,
                 shutterReadBack=None):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        mnemonic : `string`
            Camera mnemonic
        address : `tuple`
            Camera host server Internet address
        shutterType : `string`
            The type of software controlled shutter. The type can be "simple", "toggle"
            or "null". The null shutter completely disables software controlled shutter.
            The simple shutter is an EPICS PV that must be set to 0 to open the shutter
            and 1 to close the shutter. The toggle shutter uses two PVs, one that
            changes the shutter state whenever written to and another to read back the
            current shutter state.
        shutter :  `string`
            The shutter PV name. Only meaningful if the shutter type is not null.
        shutterReadBack : `string`
            The toggle shutter read back PV.
        """
        super().__init__(mnemonic)
        self.socket = socket(AF_INET, SOCK_STREAM)
        self.socket.connect(address)
        self.counting = False
        self.timer = Timer(1)
        self.subScan = False
        self.subScanStep = 0

        shutterName = mnemonic + '_shutter'

        if shutterType == 'toggle':
            self.shutter = ToggleShutter(shutterName, shutter, shutterReadback)
        elif shutterType == 'simple':
            self.shutter = SimpleShutter(shutterName, shutter)
        else:
            self.shutter = NullShutter(shutterName)

        self.shutter.close(wait=True)

        # Force clearing busy and error flags. Both busy and error flags
        # May get stuck after exceptional conditions.
        try:
            state = self.waitWhile(self.STATE_MASK_BUSY, self.URGENT_TIMEOUT)
        except RuntimeError:
            self.socket.send(b'abort\n')

            try:
                self.setState(0, self.URGENT_TIMEOUT)
            except RuntimeError as e:
                self.socket.close()
                raise e from None
Example #17
0
    def __init__(self, mnemonic, pv):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        mnemonic : `string`
            A mnemonic for the camera
        pv : `string`
            Base name of the EPICS process variable
        """
        super().__init__(mnemonic)
        self.acquireChanged = Event()
        self.acquiring = False
        self.pvAcquire = PV(pv + ':Acquire')
        self.pvAcquire.add_callback(self.statusChange)

        caput(pv + ':FileTemplate', '%s%s\0')
        caput(pv + ':FilePath', '\0')

        self.pvAcquireTime = PV(pv + ':AcquireTime')
        self.pvAcquirePeriod = PV(pv + ':AcquirePeriod')
        self.pvFilePath = PV(pv + ':FilePath')
        self.pvFileName = PV(pv + ':FileName')
        self.pvFileTemplate = PV(pv + ':FileTemplate')
        self.pvThreshold = PV(pv + ':ThresholdEnergy')
        self.pvBeamX = PV(pv + ':BeamX')
        self.pvBeamY = PV(pv + ':BeamY')
        self.pvWavelength = PV(pv + ':Wavelength')
        self.pvStartAngle = PV(pv + ':StartAngle')
        self.pvAngleIncr = PV(pv + ':AngleIncr')
        self.pvDetDist = PV(pv + ':DetDist')
        self.pvNumImages = PV(pv + ':NumImages')
        self.pvDelayTime = PV(pv + ':DelayTime')
        self.pvTriggerMode = PV(pv + ':TriggerMode')
        self.pvDet2Theta = PV(pv + ':Det2theta')
        self.pvCamserverConnectStatus = PV(pv + ':CamserverAsyn.CNCT')
        self.pvLastFileName = PV(pv + ":FullFileName_RBV")

        self.timer = Timer(self.RESPONSE_TIMEOUT)
Example #18
0
class MarCCD(StandardDevice, ICountable):
    """
    Class to control MarCCD cameras via TCP sockets.

    Examples
    --------
    >>> from shutil import move
    >>> from py4syn.epics.MarCCDClass import MarCCD
    >>> 
    >>> def getImage(host='localhost', port=2222, fileName='image.tif', shutter=''):
    ...     camera = MarCCD(name, (host, port), shutterType='simple', shutter=shutter)
    ...     camera.setCountTime(10)
    ...     camera.startCount()
    ...     camera.wait()
    ...     camera.stopCount()
    ...     camera.writeImage('/remote/' + fileName)
    ...     move('/remote/' + fileName, '/home/user/' + fileName)
    ...     camera.close()
    ...
    >>> def cameraWithoutShutter(name='', host='localhost', port=2222):
    ...     return MarCCD(name, (host, port), shutterType='null')
    ...
    >>> def acquireSetWithCorrection(camera, exposure=10, count=10, prefix='data'):
    ...     try:
    ...         camera.darkNoise()
    ...         camera.setCountTime(exposure)
    ...         camera.setSubScan(count=2)
    ...
    ...         for i in range(count):
    ...             remote = '/remote/%s-%02d.tif' % (prefix, i)
    ...             local = '/home/user/%s-%02d.tif' % (prefix, i)
    ...             camera.startCount()
    ...             camera.wait()
    ...             camera.stopCount()
    ...             camera.startCount()
    ...             camera.wait()
    ...             camera.stopCount()
    ...             camera.dezinger()
    ...             camera.correct()
    ...             camera.writeImage(remote)
    ...             move(remote, local)
    ...     finally:
    ...         camera.close()
    ...
    """

    STATE_MASK_BUSY = 0x8
    STATE_MASK_ACQUIRING = 0x30
    STATE_MASK_READING = 0x300
    STATE_MASK_CORRECTING = 0x3000
    STATE_MASK_WRITING = 0x30000
    STATE_MASK_DEZINGERING = 0x300000
    STATE_MASK_SAVING = 0x33300
    STATE_MASK_ERROR = 0x44444
    TIMEOUT = 60
    URGENT_TIMEOUT = 0.5

    def __init__(self,
                 mnemonic,
                 address,
                 shutterType,
                 shutter=None,
                 shutterReadBack=None):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        mnemonic : `string`
            Camera mnemonic
        address : `tuple`
            Camera host server Internet address
        shutterType : `string`
            The type of software controlled shutter. The type can be "simple", "toggle"
            or "null". The null shutter completely disables software controlled shutter.
            The simple shutter is an EPICS PV that must be set to 0 to open the shutter
            and 1 to close the shutter. The toggle shutter uses two PVs, one that
            changes the shutter state whenever written to and another to read back the
            current shutter state.
        shutter :  `string`
            The shutter PV name. Only meaningful if the shutter type is not null.
        shutterReadBack : `string`
            The toggle shutter read back PV.
        """
        super().__init__(mnemonic)
        self.socket = socket(AF_INET, SOCK_STREAM)
        self.socket.connect(address)
        self.counting = False
        self.timer = Timer(1)
        self.subScan = False
        self.subScanStep = 0

        shutterName = mnemonic + '_shutter'

        if shutterType == 'toggle':
            self.shutter = ToggleShutter(shutterName, shutter, shutterReadback)
        elif shutterType == 'simple':
            self.shutter = SimpleShutter(shutterName, shutter)
        else:
            self.shutter = NullShutter(shutterName)

        self.shutter.close(wait=True)

        # Force clearing busy and error flags. Both busy and error flags
        # May get stuck after exceptional conditions.
        try:
            state = self.waitWhile(self.STATE_MASK_BUSY, self.URGENT_TIMEOUT)
        except RuntimeError:
            self.socket.send(b'abort\n')

            try:
                self.setState(0, self.URGENT_TIMEOUT)
            except RuntimeError as e:
                self.socket.close()
                raise e from None

    def close(self):
        """
        Cleans up and closes camera remote connection. This method must be called when
        finishing operation with the camera.
        """
        self.stopCount()
        self.shutter.close()

        # If we caused an error, send an abort to try to fix things
        try:
            if self.getState(self.URGENT_TIMEOUT) & self.STATE_MASK_ERROR:
                self.socket.send(b'abort\n')
        except:
            pass

        self.socket.close()

    def __str__(self):
        return '%s %s' % (self.getMnemonic(), str(self.socket.getpeername()))

    def darkNoise(self, delay=0, moveShutter=True):
        """
        Prepares a dark noise image to be used as a correction image by the server.
        One of the steps after acquiring an image is to correct it by subtracting the
        dark noise image from it. This method is used to generate the dark noise image
        to be used later in the acquisitions. The method must be called with the camera
        covered. A dark noise image must be generated at least once after starting the
        MarCCD server.

        .. note::
            The following guideline is available on the MarCCD user guide: "The
            background doesn't have to be retaken for every data image taken, but
            generally should be retaken at the start of every new data set, or once every
            half hour, whichever is sooner (depending on the thermal stability of the
            hutch). For the MarCCD detector, if a mismatch in the level of the 4 quadrants
            of data frames is noticed, the bias is probably drifting and should be
            recollected (and maybe should be set to be collected more often)."

        Parameters
        ----------
        delay : `float`
            The time for each background acquisition. The MarCCD camera can either
            be calibrated with a zero delay between the acquisitions (this is called
            a bias frame acquisition in MarCCD manual), or with a non-zero (a standard
            dark frame acquisition). Note that 2 background acquisitions are done.
            They are then passed through the dezinger algorithm, which averages and
            removes outlier spots in the image.
        moveShutter : 'bool'
            Set to True to close and restore the shutter while acquiring the dark image.
        """
        closed = not self.shutter.isOpen()

        if not closed and moveShutter:
            self.shutter.close()

        self.socket.send(b'start\n')
        sleep(delay)
        self.socket.send(b'readout,2\n')
        self.socket.send(b'start\n')
        sleep(delay)
        self.socket.send(b'readout,1\n')
        self.socket.send(b'dezinger,1\n')

        if not closed and moveShutter:
            self.shutter.open()

    def getValue(self, **kwargs):
        """
        This is a dummy getValue method that always returns zero, which is part
        of the :class:`py4syn.epics.ICountable` interface. The MarCCD does not return
        a value while scanning. Instead, it stores a file with the resulting image.
        """
        return 0

    def setCountTime(self, t):
        """
        Sets the image acquisition time.

        Parameters
        ----------
        t : `float`
            Acquisition time
        """
        self.timer = Timer(t)

    def setPresetValue(self, channel, val):
        pass

    def setSubScan(self, count=2):
        """
        Configure the MarCCD object to know that each acquisition will be done in
        multiple steps. This effectivelly means that a series of acquisitions will
        be done in sequence, which will be processed together, resulting in a single
        final image. This method is mainly required for the :meth:`dezinger` method
        to work.

        Parameters
        ----------
        count : `int`
            Number of sub scans that will compose a single final image. Can be either
            1, to disable sub scan logic, or 2, which will make :meth:`stopCount` store
            images alternatedly in "scratch" (auxiliary) memory and "raw" (main) memory.
        """
        if count < 1 or count > 2:
            raise ValueError('Invalid count value')

        self.subScan = count == 2
        self.subScanStep = 0

    def startCount(self):
        """
        Starts acquiring an image. This will acquire image data until asked to stop
        with :meth:`stopCount`. This method automatically opens the shutter.

        .. note::
            Due to way the camera protocol is currently implemented, this method
            ignores the configured acquisition count time. Because of that, the proper
            way to do a timed acquisition is to follow this method call with :meth:`wait`,
            then immediatelly call :meth:`stopCount`. The :func:`py4syn.utils.scan.scan`
            function in Py4Syn executes this method sequence.

        See: :meth:`setCountTime`, :meth:`stopCount`, :meth:`wait`

            Examples
            --------
            >>> def acquire(marccd, time):
            ...     marccd.setCountTime(time)
            ...     marccd.startCount()
            ...     marccd.wait()
            ...     marccd.stopCount()
            ...
        """
        if self.counting == True:
            raise RuntimeError('Already counting')

        # Best effort wait for idle state
        try:
            self.waitWhile(self.STATE_MASK_ACQUIRING, self.URGENT_TIMEOUT)
        except RuntimeError as e:
            pass

        # Wait for reading done when doing fast exposures
        if self.timer.timeout < 1:
            try:
                self.waitWhile(self.STATE_MASK_READING, self.URGENT_TIMEOUT)
            except RuntimeError as e:
                pass

        self.socket.send(b'start\n')
        self.shutter.open()
        self.timer.mark()
        self.counting = True

    def stopCount(self):
        """
        Stops acquiring the image and stores it into server memory. The acquired image
        will be available to apply corrections and to be written to an output file.
        This method closes the shutter.

        If no call to :meth:`startCount` was done before calling this method, then
        nothing is done.
        """
        if not self.counting:
            return

        self.shutter.close()

        if self.subScan and self.subScanStep == 0:
            cmd = b'readout,2\n'
            self.subScanStep = 1
        else:
            cmd = b'readout,0\n'
            self.subScanStep = 0

        self.socket.send(cmd)
        self.counting = False

    def correct(self):
        """
        Queues image correction on the MarCCD server. After the image is corrected,
        it can be saved to a file.

        There are three corrections applied: dark noise image subtraction, flat field
        correction and geometric correction. The dark noise correction uses a dark image
        to fix the reference (zero) intensity levels for each pixel. The flat field
        correction uses a bright image to correct the gain for each pixel.
        The geometric correction fixes distortion from the fiber optic taper.

        .. note::
            The dark noise image should be frequently generated. Use the method
            :meth:`darkNoise` for that.
            
        See: :meth:`darkNoise`
        """

        self.waitWhile(
            self.STATE_MASK_DEZINGERING | self.STATE_MASK_CORRECTING
            | self.STATE_MASK_BUSY, self.TIMEOUT)
        self.socket.send(b'correct\n')

    def dezinger(self):
        """
        Apply the dezinger correction algorithm in 2 images and store the resulting
        image in the MarCCD server. The dezinger algorithm averages corresponding pixels
        from each image, but if they deviate too much, it discards the brighter one and
        keeps the lower value. This removes the "zingers", bright spots in the image,
        which are not caused by the input light.

        To be able to use the dezinger method, 2 images must be present in server
        memory. This can be accomplished by calling :meth:`setSubScan` before the
        acquisition.

        .. note::
            The following guildeline is present in MarCCD manual: "Dezingering does
            require special care that the two images are truly identical (same X-ray
            dose, same movement of the sample, etc.); otherwise the statistical test
            will yield unpredictable results. In particular, if the X-ray beam is not
            constant intensity, or the sample is decaying, then the exposure times and
            diffractometer motions must compensate for that. If there are significant
            differences between the frames, then the artifacts created by dezingering
            may yield worse results than simply using normal, single-read images with
            zingers in them. Though they are not aethetically pleasing, some kinds of
            data analysis can tolerate many zingers.

            Examples
            --------
            >>> def acquireTwiceAndDezinger(marccd, time):
            ...     marccd.setCountTime(time)
            ...     marccd.setSubScan(count=2)
            ...     marccd.startCount()
            ...     marccd.wait()
            ...     marccd.stopCount()
            ...     marccd.startCount()
            ...     marccd.wait()
            ...     marccd.stopCount()
            ...     marccd.dezinger()
            ...
        """

        # It's not clear if dezinger can be correctly queued while reading and
        # correction is being done, so just wait until everything is finished
        # to make sure that dezinger will apply to the right images. There
        # have been cases where dezinger finished before the read command finished.
        self.waitWhile(
            self.STATE_MASK_READING | self.STATE_MASK_CORRECTING
            | self.STATE_MASK_DEZINGERING | self.STATE_MASK_BUSY, self.TIMEOUT)
        self.socket.send(b'dezinger,0\n')

    def writeImage(self, fileName, wait=True):
        """
        Write the image stored in MarCCD server memory in a file. This method does not
        store the resulting image in the local machine. Since current MarCCD server
        protocol does not allow locally downloading the resulting image, this method only
        asks for the MarCCD camera server to store the image in a remote location. To
        make the file accessible locally, other means must be used, for example, by
        instructing the server to save the image in shared storage.

        Parameters
        ----------
        fileName : `string`
            Target file name in remote MarCCD server
        wait : `bool`
            Set to True if the method should block until the image is written to disk
        """
        self.waitWhile(
            self.STATE_MASK_ACQUIRING | self.STATE_MASK_READING
            | self.STATE_MASK_CORRECTING | self.STATE_MASK_WRITING
            | self.STATE_MASK_BUSY, self.TIMEOUT)
        cmd = 'writefile,%s,1\n' % fileName
        self.socket.send(cmd.encode())

        if wait:
            # Wait some time for the camera to say it started writing. This
            # is necessary because the server may return finished state (0)
            # before it started writing.
            try:
                self.waitUntil(self.STATE_MASK_WRITING, self.URGENT_TIMEOUT)
            except RuntimeError:
                pass

            self.waitWhile(
                self.STATE_MASK_ACQUIRING | self.STATE_MASK_READING
                | self.STATE_MASK_CORRECTING | self.STATE_MASK_WRITING
                | self.STATE_MASK_BUSY, self.TIMEOUT)

    def canMonitor(self):
        return False

    def canStopCount(self):
        return True

    def isCounting(self):
        return self.counting

    def wait(self):
        """
        Blocks until the configured count time passes since the call to
        :meth:`startCount`. The time amount is configured with :meth:`setCountTime`, or
        1 second by default.

        If an acquisition has not been started, this method returns immediatelly.

        See: :meth:`setCountTime`, :meth:`startCount`
        """
        if not self.isCounting():
            return

        ca.flush_io()
        self.timer.wait()

    def stateRequest(self, request, timeout=TIMEOUT):
        """
        Helper method used to get or set camera state.

        Parameters
        ----------
        request : `string`
            Command to be passed to the camera
        timeout : `float`
            Time to wait for camera answer

        Returns
        -------
        `int`
        """

        self.socket.send(request)
        timer = Timer(timeout)

        r = b''
        while b'\n' not in r and timer.wait(self.socket):
            r += self.socket.recv(1)

        if timer.expired():
            raise RuntimeError('Camera is not answering')

        r = r.strip(b'\x00\n')

        return int(r, 0)

    def getState(self, timeout=TIMEOUT):
        """
        Returns the camera state. The state can be used to check for errors, to find out
        which operations are queued or being executed and if the server is busy
        interpreting a command.

        The camera state is an integer with a 4-bit value, plus five 4-bit fields:
        acquire, read, correct, write and (highest) dezinger. The low 4-bit state value
        can be 0, for idle, 7 for bad request and 8 for busy. Each 4-bit field has
        4 flags: queued (0x1), executing (0x2), error (0x4) and reserved (0x8).
        For example, the state 0x011200 means that a read is executing, a correction is
        queued and a write is queued. The state mask 0x444444 can be used to look for an
        error on any operation. The lowest field (state) uses the value 8 to indicate
        it's busy processing a command, so state 0x8 means "interpreting command".

        Parameters
        ----------
        timeout : `float`
            Time to wait for camera answer

        Returns
        -------
        `int`
        """
        return self.stateRequest(b'get_state\n', timeout)

    def setState(self, state, timeout=TIMEOUT):
        """
        Changes the camera state bit field. This method does not change the operating
        state, just the reported integer value. It is a helper method to deal with a
        quirk in the MarCCD server that makes the error and busy bits to get stuck and
        never reset. Usually the only value that makes sense for the state is zero,
        to clear all the bits.

        See: :meth:`getState`

        Parameters
        ----------
        state : `int`
            Time to wait for camera answer
        timeout : `float`
            Time to wait for camera answer
        """
        cmd = 'set_state,%d\n' % state
        self.stateRequest(cmd.encode(), timeout)

    def waitWhileOrUntil(self, condition, timeout=TIMEOUT, until=False):
        """
        Helper method that implements :meth:`waitWhile` and :meth:`waitUntil`
        """

        state = self.getState(timeout)
        timer = Timer(timeout)

        while until ^ bool(state & condition) and timer.check():
            state = self.getState(timeout)

            if state & self.STATE_MASK_ERROR:
                raise RuntimeError('Camera returned error: %x' % state)

        if timer.expired():
            raise RuntimeError('Camera got stuck condition: %x, state: %x' %
                               (condition, state))

    def waitWhile(self, condition, timeout=TIMEOUT):
        """
        Blocks while the camera state asserts a certain condition. This method can
        be used to confirm that an operation has finished, or if the camera is reporting
        an error. The condition is a bit mask with the same meanings as described in
        :meth:`getState`. For example, calling this method with condition set to
        0x30 blocks while an aquisition is either queued or executing. Similarly,
        it's possible to block while the camera server is either processing or writing
        the image with condition set to 0x333308.

        This method detects errors automatically by raising an exception if any error
        bit is set.

        See: :meth:`getState`

        Parameters
        ----------
        condition : `int`
            State condition mask. If any of the condition bits is set, the condition
            is considered to be true.
        timeout : `float`
            Time to wait for the condition to be deasserted
        """

        self.waitWhileOrUntil(condition, timeout, until=False)

    def waitUntil(self, condition, timeout=TIMEOUT):
        """
        Blocks until the camera state asserts a certain condition. This method can
        be used to confirm that an operation has started, or if the camera is reporting
        an error. The condition is a bit mask with the same meanings as described in
        :meth:`getState`. For example, calling this method with condition set to
        0x20 blocks until an aquisition is executing.

        This method detects errors automatically by raising an exception if any error
        bit is set.

        See: :meth:`getState`

        Parameters
        ----------
        condition : `int`
            State condition mask. If any of the condition bits is set, the condition
            is considered to be true.
        timeout : `float`
            Time to wait for the condition to be deasserted
        """

        self.waitWhileOrUntil(condition, timeout, until=True)

    def waitForImage(self):
        """
        Blocks until the acquired image has been corrected and written to disk. This
        can be used any time after calling :meth:`stopCount` to make sure file
        operations can be performed on the resulting image (copied, post-processed, etc.)
        """
        # Wait some time for the camera to say it started writing. This
        # is necessary because the server may return finished state (0)
        # before it started writing.
        try:
            self.waitUntil(self.STATE_MASK_WRITING, self.URGENT_TIMEOUT)
        except RuntimeError:
            pass

        try:
            self.waitWhile(self.STATE_MASK_SAVING | self.STATE_MASK_BUSY)
        except RuntimeError:
            raise RuntimeError('Camera took too long to write image file')
Example #19
0
class Pilatus(StandardDevice, ICountable):
    """
    Class to control Pilatus cameras via EPICS.

    Examples
    --------
    >>> from shutil import move
    >>> from py4syn.epics.PilatusClass import Pilatus
    >>> from py4syn.epics.ShutterClass import SimpleShutter
    >>> 
    >>> def getImage(pv, fileName='image.tif', shutter=''):
    ...     shutter = SimpleShutter(shutter, shutter)
    ...     camera = Pilatus('pilatus', pv)
    ...     camera.setImageName('/remote/' + fileName)
    ...     camera.setCountTime(10)
    ...     camera.startCount()
    ...     shutter.open()
    ...     camera.wait()
    ...     camera.stopCount()
    ...     shutter.close()
    ...     move('/remote/' + fileName, '/home/user/' + fileName)
    ...     camera.close()
    ...
    """
    RESPONSE_TIMEOUT = 35

    def __init__(self, mnemonic, pv):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        mnemonic : `string`
            A mnemonic for the camera
        pv : `string`
            Base name of the EPICS process variable
        """
        super().__init__(mnemonic)
        self.acquireChanged = Event()
        self.acquiring = False
        self.pvAcquire = PV(pv + ':Acquire')
        self.pvAcquire.add_callback(self.statusChange)

        caput(pv + ':FileTemplate', '%s%s\0')
        caput(pv + ':FilePath', '\0')

        self.pvAcquireTime = PV(pv + ':AcquireTime')
        self.pvAcquirePeriod = PV(pv + ':AcquirePeriod')
        self.pvFilePath = PV(pv + ':FilePath')
        self.pvFileName = PV(pv + ':FileName')
        self.pvFileTemplate = PV(pv + ':FileTemplate')
        self.pvThreshold = PV(pv + ':ThresholdEnergy')
        self.pvBeamX = PV(pv + ':BeamX')
        self.pvBeamY = PV(pv + ':BeamY')
        self.pvWavelength = PV(pv + ':Wavelength')
        self.pvStartAngle = PV(pv + ':StartAngle')
        self.pvAngleIncr = PV(pv + ':AngleIncr')
        self.pvDetDist = PV(pv + ':DetDist')
        self.pvNumImages = PV(pv + ':NumImages')
        self.pvDelayTime = PV(pv + ':DelayTime')
        self.pvTriggerMode = PV(pv + ':TriggerMode')
        self.pvDet2Theta = PV(pv + ':Det2theta')
        self.pvCamserverConnectStatus = PV(pv + ':CamserverAsyn.CNCT')
        self.pvLastFileName = PV(pv + ":FullFileName_RBV")

        self.timer = Timer(self.RESPONSE_TIMEOUT)

    def setImageName(self, name):
        """
        Sets the output image file name. The image will be saved with this name
        after the acquisition.
        
        Parameters
        ----------
        name : `string`
            The full pathname of the image.
        """
        self.pvFileName.put(name + "\0", wait=True)

    def setFilePath(self, path):
        """
        Sets the output image file path. The image will be saved in this location
        after the acquisition.
        
        Parameters
        ----------
        name : `string`
            The path of location to save the image.
        """
        self.pvFilePath.put(path + "\0", wait=True)

    def getFilePath(self):
        """
        Returns the path where image file should be saved.
        """
        return self.pvFilePath.get(as_string=True)

    def setFileName(self, name):
        """
        Sets the output image file name. The image will be saved with this name
        after the acquisition.
        
        Parameters
        ----------
        name : `string`
            The name of image to save.
        """
        self.pvFileName.put(name + "\0", wait=True)

    def getFileName(self):
        """
        Returns the name of the image to be saved.
        """
        return self.pvFileName.get(as_string=True)

    def setFileTemplate(self, template="%s%s"):
        self.pvFileTemplate.put(template + "\0", wait=True)

    def getFileTemplate(self):
        return self.pvFileTemplate.get(as_string=True)

    def statusChange(self, value, **kw):
        """
        Helper callback used to wait for the end of the acquisition.
        """
        self.acquiring = value
        self.acquireChanged.set()

    def close(self):
        """
        Stops an ongoing acquisition, if any, and puts the EPICS IOC in idle state.
        """
        self.pvAcquire.put(0, wait=True)

    def getValue(self, **kwargs):
        """
        This is a dummy method that always returns zero, which is part of the
        :class:`py4syn.epics.ICountable` interface. Pilatus does not return
        a value while scanning. Instead, it stores a file with the resulting image.
        """
        return 0

    def setCountTime(self, t):
        """
        Sets the image acquisition time.

        Parameters
        ----------
        t : `float`
            Acquisition time
        """
        self.pvAcquireTime.put(t, wait=True)
        self.pvAcquirePeriod.put(t + READOUTTIME, wait=True)
        self.timer = Timer(t + self.RESPONSE_TIMEOUT)

    def getAcquireTime(self):
        return self.pvAcquireTime.get()

    def setAcquirePeriod(self, period):
        """
        Sets the acquire period.

        Parameters
        ----------
        t : `float`
            Acquisition period
        """
        self.pvAcquirePeriod.put(period, wait=True)

    def getAcquirePeriod(self):
        return self.pvAcquirePeriod.get()

    def setPresetValue(self, channel, val):
        """
        Dummy method to set initial counter value.
        """
        pass

    def startCount(self):
        """
        Starts acquiring an image. It will acquire for the duration set with
        :meth:`setCountTime`. The resulting file will be stored in the file set with
        :meth:`setImageName`.

        See: :meth:`setCountTime`, :meth:`setImageName`

            Examples
            --------
            >>> def acquire(pilatus, time, filename):
            ...     pilatus.setCountTime(time)
            ...     pilatus.setImageName(filename)
            ...     pilatus.startCount()
            ...     pilatus.wait()
            ...     pilatus.stopCount()
            ...
        """
        if self.acquiring:
            raise RuntimeError('Already counting')

        self.acquiring = True
        self.pvAcquire.put(1)
        self.timer.mark()

    def stopCount(self):
        """
        Stops acquiring the image. This method simply calls :meth:`close`.
        
        See: :meth:`close`
        """
        self.close()

    def canMonitor(self):
        """
        Returns false indicating that Pilatus cannot be used as a counter monitor.
        """
        return False

    def canStopCount(self):
        """
        Returns true indicating that Pilatus has a stop command.
        """
        return True

    def isCounting(self):
        """
        Returns true if the camera is acquiring an image, or false otherwise.

        Returns
        -------
        `bool`
        """
        return self.acquiring

    def wait(self):
        """
        Blocks until the acquisition completes.
        """
        if not self.acquiring:
            return

        self.acquireChanged.clear()
        while self.acquiring and self.timer.check():
            self.acquireChanged.wait(5)
            self.acquireChanged.clear()

        if self.timer.expired():
            raise RuntimeError('Camera is not answering')

    def setThreshold(self, threshold, wait=True):
        self.pvThreshold.put(threshold, wait=wait)

    def getThreshold(self):
        return self.pvThreshold.get()

    def setBeamPosition(self, position=[0, 0]):
        self.pvBeamX.put(position[0], wait=True)
        self.pvBeamY.put(position[1], wait=True)

    def getBeamPosition(self):
        return [self.pvBeamX.get(), self.pvBeamY.get()]

    def setWavelength(self, wavelength):
        self.pvWavelength.put(wavelength, wait=True)

    def getWavelength(self):
        return self.pvWavelength.get()

    def setStartAngle(self, start):
        self.pvStartAngle.put(start, wait=True)

    def getStartAngle(self):
        return self.pvStartAngle.get()

    def setAngleIncr(self, incr):
        self.pvAngleIncr.put(incr, wait=True)

    def getAngleIncr(self):
        return self.pvAngleIncr.get()

    def setDetDist(self, distance):
        self.pvDetDist.put(distance, wait=True)

    def getDetDist(self):
        return self.pvDetDist.get()

    def setNumImages(self, num):
        self.pvNumImages.put(num, wait=True)

    def getNumImages(self):
        return self.pvNumImages.get()

    def setDelayTime(self, delay):
        self.pvDelayTime.put(delay, wait=True)

    def getDelayTime(self):
        return self.pvDelayTime.get()

    def setTriggerMode(self, mode):
        """
        Trigger mode

        Parameters
        ----------
        mode  : `int`
            0 : Internal
            1 : Ext. Enable
            2 : Ext. Trigger
            3 : Mult. Trigger
            4 : Alignment
        """
        self.pvTriggerMode.put(mode, wait=True)

    def getTriggerMode(self):
        return self.pvTriggerMode.get()

    def setDet2Theta(self, det2theta):
        self.pvDet2Theta.put(det2theta, wait=True)

    def getDet2Theta(self):
        return self.pvDet2Theta.get()

    def isCamserverConnected(self):
        return (self.pvCamserverConnectStatus.get() == 1)
Example #20
0
class MarCCD(StandardDevice, ICountable):
    """
    Class to control MarCCD cameras via TCP sockets.

    Examples
    --------
    >>> from shutil import move
    >>> from py4syn.epics.MarCCDClass import MarCCD
    >>> 
    >>> def getImage(host='localhost', port=2222, fileName='image.tif', shutter=''):
    ...     camera = MarCCD(name, (host, port), shutterType='simple', shutter=shutter)
    ...     camera.setCountTime(10)
    ...     camera.startCount()
    ...     camera.wait()
    ...     camera.stopCount()
    ...     camera.writeImage('/remote/' + fileName)
    ...     move('/remote/' + fileName, '/home/user/' + fileName)
    ...     camera.close()
    ...
    >>> def cameraWithoutShutter(name='', host='localhost', port=2222):
    ...     return MarCCD(name, (host, port), shutterType='null')
    ...
    >>> def acquireSetWithCorrection(camera, exposure=10, count=10, prefix='data'):
    ...     try:
    ...         camera.darkNoise()
    ...         camera.setCountTime(exposure)
    ...         camera.setSubScan(count=2)
    ...
    ...         for i in range(count):
    ...             remote = '/remote/%s-%02d.tif' % (prefix, i)
    ...             local = '/home/user/%s-%02d.tif' % (prefix, i)
    ...             camera.startCount()
    ...             camera.wait()
    ...             camera.stopCount()
    ...             camera.startCount()
    ...             camera.wait()
    ...             camera.stopCount()
    ...             camera.dezinger()
    ...             camera.correct()
    ...             camera.writeImage(remote)
    ...             move(remote, local)
    ...     finally:
    ...         camera.close()
    ...
    """

    STATE_MASK_BUSY = 0x8
    STATE_MASK_ACQUIRING = 0x30
    STATE_MASK_READING = 0x300
    STATE_MASK_CORRECTING = 0x3000
    STATE_MASK_WRITING = 0x30000
    STATE_MASK_DEZINGERING = 0x300000
    STATE_MASK_SAVING = 0x33300
    STATE_MASK_ERROR = 0x44444
    TIMEOUT = 60
    URGENT_TIMEOUT = 0.5

    def __init__(self, mnemonic, address, shutterType, shutter=None,
                 shutterReadBack=None):
        """
        **Constructor**
        See :class:`py4syn.epics.StandardDevice`

        Parameters
        ----------
        mnemonic : `string`
            Camera mnemonic
        address : `tuple`
            Camera host server Internet address
        shutterType : `string`
            The type of software controlled shutter. The type can be "simple", "toggle"
            or "null". The null shutter completely disables software controlled shutter.
            The simple shutter is an EPICS PV that must be set to 0 to open the shutter
            and 1 to close the shutter. The toggle shutter uses two PVs, one that
            changes the shutter state whenever written to and another to read back the
            current shutter state.
        shutter :  `string`
            The shutter PV name. Only meaningful if the shutter type is not null.
        shutterReadBack : `string`
            The toggle shutter read back PV.
        """
        super().__init__(mnemonic)
        self.socket = socket(AF_INET, SOCK_STREAM)
        self.socket.connect(address)
        self.counting = False
        self.timer = Timer(1)
        self.subScan = False
        self.subScanStep = 0

        shutterName = mnemonic + '_shutter'

        if shutterType == 'toggle':
            self.shutter = ToggleShutter(shutterName, shutter, shutterReadback)
        elif shutterType == 'simple':
            self.shutter = SimpleShutter(shutterName, shutter)
        else:
            self.shutter = NullShutter(shutterName)

        self.shutter.close(wait=True)

        # Force clearing busy and error flags. Both busy and error flags
        # May get stuck after exceptional conditions.
        try:
            state = self.waitWhile(self.STATE_MASK_BUSY, self.URGENT_TIMEOUT)
        except RuntimeError:
            self.socket.send(b'abort\n')

            try:
                self.setState(0, self.URGENT_TIMEOUT)
            except RuntimeError as e:
                self.socket.close()
                raise e from None

    def close(self):
        """
        Cleans up and closes camera remote connection. This method must be called when
        finishing operation with the camera.
        """
        self.stopCount()
        self.shutter.close()

        # If we caused an error, send an abort to try to fix things
        try:
            if self.getState(self.URGENT_TIMEOUT) & self.STATE_MASK_ERROR:
                self.socket.send(b'abort\n')
        except:
            pass

        self.socket.close()

    def __str__(self):
        return '%s %s' % (self.getMnemonic(), str(self.socket.getpeername()))

    def darkNoise(self, delay=0, moveShutter=True):
        """
        Prepares a dark noise image to be used as a correction image by the server.
        One of the steps after acquiring an image is to correct it by subtracting the
        dark noise image from it. This method is used to generate the dark noise image
        to be used later in the acquisitions. The method must be called with the camera
        covered. A dark noise image must be generated at least once after starting the
        MarCCD server.

        .. note::
            The following guideline is available on the MarCCD user guide: "The
            background doesn't have to be retaken for every data image taken, but
            generally should be retaken at the start of every new data set, or once every
            half hour, whichever is sooner (depending on the thermal stability of the
            hutch). For the MarCCD detector, if a mismatch in the level of the 4 quadrants
            of data frames is noticed, the bias is probably drifting and should be
            recollected (and maybe should be set to be collected more often)."

        Parameters
        ----------
        delay : `float`
            The time for each background acquisition. The MarCCD camera can either
            be calibrated with a zero delay between the acquisitions (this is called
            a bias frame acquisition in MarCCD manual), or with a non-zero (a standard
            dark frame acquisition). Note that 2 background acquisitions are done.
            They are then passed through the dezinger algorithm, which averages and
            removes outlier spots in the image.
        moveShutter : 'bool'
            Set to True to close and restore the shutter while acquiring the dark image.
        """
        closed = not self.shutter.isOpen()

        if not closed and moveShutter:
            self.shutter.close()

        self.socket.send(b'start\n')
        sleep(delay)
        self.socket.send(b'readout,2\n')
        self.socket.send(b'start\n')
        sleep(delay)
        self.socket.send(b'readout,1\n')
        self.socket.send(b'dezinger,1\n')

        if not closed and moveShutter:
            self.shutter.open()

    def getValue(self, **kwargs):
        """
        This is a dummy getValue method that always returns zero, which is part
        of the :class:`py4syn.epics.ICountable` interface. The MarCCD does not return
        a value while scanning. Instead, it stores a file with the resulting image.
        """
        return 0

    def setCountTime(self, t):
        """
        Sets the image acquisition time.

        Parameters
        ----------
        t : `float`
            Acquisition time
        """
        self.timer = Timer(t)

    def setPresetValue(self, channel, val):
        pass

    def setSubScan(self, count=2):
        """
        Configure the MarCCD object to know that each acquisition will be done in
        multiple steps. This effectivelly means that a series of acquisitions will
        be done in sequence, which will be processed together, resulting in a single
        final image. This method is mainly required for the :meth:`dezinger` method
        to work.

        Parameters
        ----------
        count : `int`
            Number of sub scans that will compose a single final image. Can be either
            1, to disable sub scan logic, or 2, which will make :meth:`stopCount` store
            images alternatedly in "scratch" (auxiliary) memory and "raw" (main) memory.
        """
        if count < 1 or count > 2:
            raise ValueError('Invalid count value')

        self.subScan = count == 2
        self.subScanStep = 0

    def startCount(self):
        """
        Starts acquiring an image. This will acquire image data until asked to stop
        with :meth:`stopCount`. This method automatically opens the shutter.

        .. note::
            Due to way the camera protocol is currently implemented, this method
            ignores the configured acquisition count time. Because of that, the proper
            way to do a timed acquisition is to follow this method call with :meth:`wait`,
            then immediatelly call :meth:`stopCount`. The :func:`py4syn.utils.scan.scan`
            function in Py4Syn executes this method sequence.

        See: :meth:`setCountTime`, :meth:`stopCount`, :meth:`wait`

            Examples
            --------
            >>> def acquire(marccd, time):
            ...     marccd.setCountTime(time)
            ...     marccd.startCount()
            ...     marccd.wait()
            ...     marccd.stopCount()
            ...
        """
        if self.counting == True:
            raise RuntimeError('Already counting')

        # Best effort wait for idle state
        try:
            self.waitWhile(self.STATE_MASK_ACQUIRING, self.URGENT_TIMEOUT)
        except RuntimeError as e:
            pass

        # Wait for reading done when doing fast exposures
        if self.timer.timeout < 1:
            try:
                self.waitWhile(self.STATE_MASK_READING, self.URGENT_TIMEOUT)
            except RuntimeError as e:
                pass

        self.socket.send(b'start\n')
        self.shutter.open()
        self.timer.mark()
        self.counting = True

    def stopCount(self):
        """
        Stops acquiring the image and stores it into server memory. The acquired image
        will be available to apply corrections and to be written to an output file.
        This method closes the shutter.

        If no call to :meth:`startCount` was done before calling this method, then
        nothing is done.
        """
        if not self.counting:
            return

        self.shutter.close()

        if self.subScan and self.subScanStep == 0:
            cmd = b'readout,2\n'
            self.subScanStep = 1
        else:
            cmd = b'readout,0\n'
            self.subScanStep = 0

        self.socket.send(cmd)
        self.counting = False

    def correct(self):
        """
        Queues image correction on the MarCCD server. After the image is corrected,
        it can be saved to a file.

        There are three corrections applied: dark noise image subtraction, flat field
        correction and geometric correction. The dark noise correction uses a dark image
        to fix the reference (zero) intensity levels for each pixel. The flat field
        correction uses a bright image to correct the gain for each pixel.
        The geometric correction fixes distortion from the fiber optic taper.

        .. note::
            The dark noise image should be frequently generated. Use the method
            :meth:`darkNoise` for that.
            
        See: :meth:`darkNoise`
        """

        self.waitWhile(self.STATE_MASK_DEZINGERING | self.STATE_MASK_CORRECTING |
                       self.STATE_MASK_BUSY, self.TIMEOUT)
        self.socket.send(b'correct\n')

    def dezinger(self):
        """
        Apply the dezinger correction algorithm in 2 images and store the resulting
        image in the MarCCD server. The dezinger algorithm averages corresponding pixels
        from each image, but if they deviate too much, it discards the brighter one and
        keeps the lower value. This removes the "zingers", bright spots in the image,
        which are not caused by the input light.

        To be able to use the dezinger method, 2 images must be present in server
        memory. This can be accomplished by calling :meth:`setSubScan` before the
        acquisition.

        .. note::
            The following guildeline is present in MarCCD manual: "Dezingering does
            require special care that the two images are truly identical (same X-ray
            dose, same movement of the sample, etc.); otherwise the statistical test
            will yield unpredictable results. In particular, if the X-ray beam is not
            constant intensity, or the sample is decaying, then the exposure times and
            diffractometer motions must compensate for that. If there are significant
            differences between the frames, then the artifacts created by dezingering
            may yield worse results than simply using normal, single-read images with
            zingers in them. Though they are not aethetically pleasing, some kinds of
            data analysis can tolerate many zingers.

            Examples
            --------
            >>> def acquireTwiceAndDezinger(marccd, time):
            ...     marccd.setCountTime(time)
            ...     marccd.setSubScan(count=2)
            ...     marccd.startCount()
            ...     marccd.wait()
            ...     marccd.stopCount()
            ...     marccd.startCount()
            ...     marccd.wait()
            ...     marccd.stopCount()
            ...     marccd.dezinger()
            ...
        """

        # It's not clear if dezinger can be correctly queued while reading and
        # correction is being done, so just wait until everything is finished
        # to make sure that dezinger will apply to the right images. There
        # have been cases where dezinger finished before the read command finished.
        self.waitWhile(self.STATE_MASK_READING | self.STATE_MASK_CORRECTING |
                       self.STATE_MASK_DEZINGERING | self.STATE_MASK_BUSY, self.TIMEOUT)
        self.socket.send(b'dezinger,0\n')

    def writeImage(self, fileName, wait=True):
        """
        Write the image stored in MarCCD server memory in a file. This method does not
        store the resulting image in the local machine. Since current MarCCD server
        protocol does not allow locally downloading the resulting image, this method only
        asks for the MarCCD camera server to store the image in a remote location. To
        make the file accessible locally, other means must be used, for example, by
        instructing the server to save the image in shared storage.

        Parameters
        ----------
        fileName : `string`
            Target file name in remote MarCCD server
        wait : `bool`
            Set to True if the method should block until the image is written to disk
        """
        self.waitWhile(self.STATE_MASK_ACQUIRING | self.STATE_MASK_READING |
                       self.STATE_MASK_CORRECTING |self.STATE_MASK_WRITING |
                       self.STATE_MASK_BUSY, self.TIMEOUT)
        cmd = 'writefile,%s,1\n' % fileName
        self.socket.send(cmd.encode())

        if wait:
            # Wait some time for the camera to say it started writing. This
            # is necessary because the server may return finished state (0)
            # before it started writing.
            try:
                self.waitUntil(self.STATE_MASK_WRITING, self.URGENT_TIMEOUT)
            except RuntimeError:
                pass

            self.waitWhile(self.STATE_MASK_ACQUIRING | self.STATE_MASK_READING |
                           self.STATE_MASK_CORRECTING |self.STATE_MASK_WRITING |
                           self.STATE_MASK_BUSY, self.TIMEOUT)

    def canMonitor(self):
        return False

    def canStopCount(self):
        return True

    def isCounting(self):
        return self.counting

    def wait(self):
        """
        Blocks until the configured count time passes since the call to
        :meth:`startCount`. The time amount is configured with :meth:`setCountTime`, or
        1 second by default.

        If an acquisition has not been started, this method returns immediatelly.

        See: :meth:`setCountTime`, :meth:`startCount`
        """
        if not self.isCounting():
            return

        ca.flush_io()
        self.timer.wait()

    def stateRequest(self, request, timeout=TIMEOUT):
        """
        Helper method used to get or set camera state.

        Parameters
        ----------
        request : `string`
            Command to be passed to the camera
        timeout : `float`
            Time to wait for camera answer

        Returns
        -------
        `int`
        """

        self.socket.send(request)
        timer = Timer(timeout)

        r = b''
        while b'\n' not in r and timer.wait(self.socket):
            r += self.socket.recv(1)

        if timer.expired():
            raise RuntimeError('Camera is not answering')

        r = r.strip(b'\x00\n')

        return int(r, 0)

    def getState(self, timeout=TIMEOUT):
        """
        Returns the camera state. The state can be used to check for errors, to find out
        which operations are queued or being executed and if the server is busy
        interpreting a command.

        The camera state is an integer with a 4-bit value, plus five 4-bit fields:
        acquire, read, correct, write and (highest) dezinger. The low 4-bit state value
        can be 0, for idle, 7 for bad request and 8 for busy. Each 4-bit field has
        4 flags: queued (0x1), executing (0x2), error (0x4) and reserved (0x8).
        For example, the state 0x011200 means that a read is executing, a correction is
        queued and a write is queued. The state mask 0x444444 can be used to look for an
        error on any operation. The lowest field (state) uses the value 8 to indicate
        it's busy processing a command, so state 0x8 means "interpreting command".

        Parameters
        ----------
        timeout : `float`
            Time to wait for camera answer

        Returns
        -------
        `int`
        """
        return self.stateRequest(b'get_state\n', timeout)

    def setState(self, state, timeout=TIMEOUT):
        """
        Changes the camera state bit field. This method does not change the operating
        state, just the reported integer value. It is a helper method to deal with a
        quirk in the MarCCD server that makes the error and busy bits to get stuck and
        never reset. Usually the only value that makes sense for the state is zero,
        to clear all the bits.

        See: :meth:`getState`

        Parameters
        ----------
        state : `int`
            Time to wait for camera answer
        timeout : `float`
            Time to wait for camera answer
        """
        cmd = 'set_state,%d\n' % state
        self.stateRequest(cmd.encode(), timeout)

    def waitWhileOrUntil(self, condition, timeout=TIMEOUT, until=False):
        """
        Helper method that implements :meth:`waitWhile` and :meth:`waitUntil`
        """

        state = self.getState(timeout)
        timer = Timer(timeout)

        while until ^ bool(state & condition) and timer.check():
            state = self.getState(timeout)

            if state & self.STATE_MASK_ERROR:
                raise RuntimeError('Camera returned error: %x' % state)

        if timer.expired():
            raise RuntimeError('Camera got stuck condition: %x, state: %x' %
                               (condition, state))

    def waitWhile(self, condition, timeout=TIMEOUT):
        """
        Blocks while the camera state asserts a certain condition. This method can
        be used to confirm that an operation has finished, or if the camera is reporting
        an error. The condition is a bit mask with the same meanings as described in
        :meth:`getState`. For example, calling this method with condition set to
        0x30 blocks while an aquisition is either queued or executing. Similarly,
        it's possible to block while the camera server is either processing or writing
        the image with condition set to 0x333308.

        This method detects errors automatically by raising an exception if any error
        bit is set.

        See: :meth:`getState`

        Parameters
        ----------
        condition : `int`
            State condition mask. If any of the condition bits is set, the condition
            is considered to be true.
        timeout : `float`
            Time to wait for the condition to be deasserted
        """

        self.waitWhileOrUntil(condition, timeout, until=False)

    def waitUntil(self, condition, timeout=TIMEOUT):
        """
        Blocks until the camera state asserts a certain condition. This method can
        be used to confirm that an operation has started, or if the camera is reporting
        an error. The condition is a bit mask with the same meanings as described in
        :meth:`getState`. For example, calling this method with condition set to
        0x20 blocks until an aquisition is executing.

        This method detects errors automatically by raising an exception if any error
        bit is set.

        See: :meth:`getState`

        Parameters
        ----------
        condition : `int`
            State condition mask. If any of the condition bits is set, the condition
            is considered to be true.
        timeout : `float`
            Time to wait for the condition to be deasserted
        """

        self.waitWhileOrUntil(condition, timeout, until=True)

    def waitForImage(self):
        """
        Blocks until the acquired image has been corrected and written to disk. This
        can be used any time after calling :meth:`stopCount` to make sure file
        operations can be performed on the resulting image (copied, post-processed, etc.)
        """
        # Wait some time for the camera to say it started writing. This
        # is necessary because the server may return finished state (0)
        # before it started writing.
        try:
            self.waitUntil(self.STATE_MASK_WRITING, self.URGENT_TIMEOUT)
        except RuntimeError:
            pass

        try:
            self.waitWhile(self.STATE_MASK_SAVING | self.STATE_MASK_BUSY)
        except RuntimeError:
            raise RuntimeError('Camera took too long to write image file')
Example #21
0
class Dxp(ImageHDF):

    # CONSTRUCTOR OF DXP CLASS
    def __init__(self,
                 mnemonic,
                 numberOfChannels=4,
                 numberOfRois=32,
                 pv=None,
                 dxpType="mca",
                 responseTimeout=15,
                 output="./out",
                 numPoints=2048):
        """ Constructor
        responseTimeout : how much time to wait dxp answer
        imageDeep : how many points are collected each time
        """
        super().__init__(mnemonic, numPoints, output, dxpType)

        self.dxpType = dxpType
        self.acquireChanged = Event()
        self.acquiring = False

        # determines the start of counting
        self.pvDxpEraseStart = PV(pv + ":EraseStart.VAL")
        # determines mode of counting (Live Time, Real Time, ...)
        self.pvDxpPresetMode = PV(pv + ":PresetMode.VAL")

        self.pvDxpStop = PV(pv + ":StopAll.VAL")
        # store all channels
        self.pvDxpChannels = []
        # store ROIs
        self.pvDxpRois = []

        # store Acquire Time for each channel
        self.pvDxpAcquireTime = []
        self.pvDxpRealTime = []

        for c in range(0, numberOfChannels):
            # store each channel
            self.pvDxpChannels.append(PV(pv + ":" + dxpType + str(c + 1)))
            # for each channel store the PV for AcquireTime
            self.pvDxpAcquireTime.append(
                PV(pv + ":" + dxpType + "%d.PLTM" % (c + 1)))
            # real time
            self.pvDxpRealTime.append(
                PV(pv + ":" + dxpType + "%d.ERTM" % (c + 1)))
            self.pvDxpRois.append([])
            # storeing each ROI in your channel
            for r in range(0, numberOfRois):
                self.pvDxpRois[c].append(
                    PV(pv + ":" + dxpType + str(c + 1) + '.R' + str(r)))

        self.pvDxpAcquire = PV(pv + ":Acquiring")
        self.pvDxpAcquire.add_callback(self.statusChange)
        self.channels = numberOfChannels
        self.rois = numberOfRois

        self.responseTimeout = responseTimeout
        self.timer = Timer(self.responseTimeout)

    def statusChange(self, value, **kw):
        """
        Helper callback used to wait for the end of the acquisition.
        """
        self.acquiring = value
        # threads waiting are awakened
        self.acquireChanged.set()

    def setCountTime(self, time):
        """
        Method to set the count time of a scaler device.

        Parameters
        ----------
        time : `float`
            Count time to set to scaler device .

        Returns
        -------
        out : None
        """
        for i in range(0, self.channels):
            self.pvDxpAcquireTime[i].put(time, wait=True)

        # This make long exposure time works
        if (self.responseTimeout < time * 0.4):
            self.responseTimeout = time * 0.4

        self.timer = Timer(time + self.responseTimeout)

    def getCountTime(self):
        # AcquireTimes are the same
        return self.pvDxpAcquireTime[0].get()

    def getRealTime(self):
        """Return the Real Time"""
        if self.channels == 1:
            return self.pvDxpRealTime[0].get()
        else:
            times = []
            for i in range(0, self.channels):
                times.append(self.pvDxpRealTime[i].get())
            return times

    def setCountStop(self):
        self.pvDxpStop.put(1, wait=True)

    def getValueChannel(self, **kwargs):
        """Return intensity
        channel is on format mcaC.Rr, where C is  the channel and
        r is the ROI"""
        channel = kwargs['channel']
        c = int(channel[CHANNELPOSITION]) - 1
        if (len(channel) > ROIPOSITION):
            r = int(channel[ROIPOSITION])
            return self.pvDxpRois[c][r].get()
        else:
            self.saveSpectrum(c, **kwargs)
            return 1.0

    def saveSpectrum(self, ch, **kwargs):
        '''save the spectrum intensity in a mca file or an hdf file
        This method load spectrum from a PV and then save it to HDF file'''
        self.pvDxpPresetMode.put("Live time")
        self.ch = ch
        self.spectrum = self.pvDxpChannels[self.ch].get(as_numpy=True)

        if self.image is None:
            # if is a point, prefix is different
            self.prefix = self.dxpType + str(self.ch)

        super().saveSpectrum()

    def isCountRunning(self):
        return (self.pvDxpAcquire.get())

    def wait(self):
        """
        Blocks until the acquisition completes.
        """
        if self.acquiring is False:
            return

        self.acquireChanged.clear()
        # while acquiring and not time out waits
        # TODO: find a better way to do this
        while self.acquiring and self.timer.check():
            self.acquireChanged.wait(0.001)
            self.acquireChanged.clear()

        if self.timer.expired():
            raise RuntimeError('DXP is not answering')

    def canMonitor(self):
        """ Returns false indcating Dxp cannot be use as a counter monitor"""
        return False

    def canStopCount(self):
        """
        Returns true indicating that Dxp has a stop command.
        """
        return True

    def getValue(self, **kwargs):
        """
        This is a dummy method that always returns zero, which is part of the
        :class:`py4syn.epics.ICountable` interface. Dxp does not return
        a value while scanning. Instead, it stores a mca file with result .
        """
        if (kwargs):
            return self.getValueChannel(**kwargs)
        return self.getValueChannel()

    def isCounting(self):
        return self.acquiring

    def startCount(self):
        """ Starts acquiring an spectrum
        It's necessary to call setCounTime before"""
        if self.acquiring:
            raise RuntimeError('Already counting')

        self.acquiring = True
        self.pvDxpEraseStart.put(1)
        # resets initial time value
        self.timer.mark()

    def stopCount(self):
        self.setCountStop()

    def setPresetValue(self, channel, val):
        """Dummy method"""
        pass

    def close(self):
        """Stops an ongoing acquisition, if any, and puts the EPICS IOC in
        idle state."""
        self.pvDxpStop.put(1, wait=True)

    def startCollectImage(self, rows=0, cols=0):
        """Start to collect an image
        When collect an image, the points will be  saved on a hdf file"""
        super().startCollectImage("int32", rows, cols)

    def setNormValue(self, value):
        """Applies normalization"""
        if self.image is None:
            self.prefix = self.dxpType + str(self.ch)

        super().setNormValue(value)
Example #22
0
class OceanOpticsSpectrometer(ImageHDF):
    # CONSTRUCTOR OF Ocean CLASS
    def __init__(self,
                 mnemonic,
                 pv=None,
                 responseTimeout=15,
                 output="./out",
                 numPoints=1044,
                 mcas=False):
        """Constructor
        responseTimeout : how much time to wait qe65000 answer
        numPoints : how many points are collected each time
        """
        super().__init__(mnemonic, numPoints, output, 'ocean')
        self.acquireChanged = Event()
        self.acquiring = False

        try:
            # determines the start of counting
            self.pvStart = PV(pv + ":Acquire")
            # determines mode of Acquisition (Single,Continous, Dark Spectrum)
            self.pvAcquireMode = PV(pv + ":AcquisitionMode")

            # use darkcorrection
            self.pvDarkCorrection = PV(pv + ":ElectricalDark")

            # spectrum
            self.pvSpectrum = PV(pv + ":Spectra")
            self.pvSpectrumCorrected = PV(pv + ":DarkCorrectedSpectra")

            # set Acquire Time
            self.pvAcquireTime = PV(pv + ":SetIntegration")

            # integration Time
            self.pvTime = PV(pv + ":IntegrationTime:Value")

            # control the end of acquire process
            self.pvAcquire = PV(pv + ":Acquiring")
            self.pvAcquire.add_callback(self.statusChange)

            # acquisition mode
            self.pvAcMode = PV(pv + ":AcquisitionMode")
            # set to single mode
            self.pvAcMode.put("Single")

            # axis Spectra
            pvAxis = PV(pv + ":SpectraAxis")
            self.axis = pvAxis.get(as_numpy=True)[:self.numPoints]

            # regions of interest
            self.ROIS = []

            self.mcas = mcas

            self.responseTimeout = responseTimeout
            self.timer = Timer(self.responseTimeout)
        except TypeError:
            raise RuntimeError('PV not found')
        except ValueError:
            raise RuntimeError('Device is offline')

    def statusChange(self, value, **kw):
        """
        Helper callback used to wait for the end of the acquisition.
        """
        if value == 0:
            self.acquiring = False
        else:
            self.acquiring = True
        # threads waiting are awakened
        self.acquireChanged.set()

    def setCountTime(self, time):
        """
        Method to set the count time of a scaler device.

        Parameters
        ----------
        time : `float`
            Count time to set to scaler device .

        Returns
        -------
        out : None
        """
        self.pvTime.put(time, wait=True)
        self.timer = Timer(time + self.responseTimeout)

    def getCountTime(self):
        return self.pvTime.get()

    def setCountStop(self):
        # TODO: test
        # Work only when in continuos mode
        pass

    def close(self):
        self.setCountStop()

    def saveUniquePoint(self, data, fmt, suffixName=""):
        self.mcaFile = super().nameFile(self.output, self.prefix + suffixName,
                                        "mca")
        np.savetxt(self.mcaFile, data, fmt=fmt)

    def saveSpectrum(self, **kwargs):
        ''' save the spectrum intensity in a mca file or an hdf file '''
        dark = self.pvDarkCorrection.get()

        # the spectra come from different pv if use darkcorrection
        if dark == 1:
            allSpectrum =\
                self.pvSpectrumCorrected.get(as_numpy=True)[:self.numPoints]
        else:
            allSpectrum = self.pvSpectrum.get(as_numpy=True)[:self.numPoints]

        self.spectrum = allSpectrum

        suffix = ""
        if self.image:
            super().saveSpectrum()

        if not self.image or self.mcas:
            self.saveUniquePoint(
                np.array([self.axis, self.spectrum]).T, "%f\t%f")

        # there are ROIS to save / works only for points
        if len(self.ROIS) > 0 and not self.image:
            i = 1
            for mini, maxi in self.ROIS:
                # get the spectrum positions
                start = bisect(self.axis, mini)
                end = bisect(self.axis, maxi)
                roi = allSpectrum[start:end]
                self.spectrum = roi
                data = np.array([self.axis[start:end], self.spectrum]).T
                self.saveUniquePoint(data,
                                     "%f\t%f",
                                     suffixName="_ROI" + str(i))
                i += 1

    def isCountRunning(self):
        return (self.acquiring)

    def wait(self):
        """
        Blocks until the acquisition completes.
        """
        if self.acquiring is False:
            return

        self.acquireChanged.clear()
        # while acquiring and not time out waits
        # TODO: find a better way to do this
        while self.acquiring and self.timer.check():
            self.acquireChanged.wait(0.001)
            self.acquireChanged.clear()

        if self.timer.expired():
            raise RuntimeError('Ocean is not answering')

    def canMonitor(self):
        """ Returns false indicating cannot be use as a counter monitor"""
        return False

    def canStopCount(self):
        """
        Returns true indicating that Dxp has a stop command.
        """
        return False

    def getValue(self, **kwargs):
        """Return intensity
        It's a dummy method, always return 1.0. """
        self.saveSpectrum()
        return 1.0

    def isCounting(self):
        return self.acquiring

    def startCount(self):
        """ Starts acquiring an spectrum
        It's necessary to call setCounTime before"""

        if self.acquiring:
            raise RuntimeError('Already counting')

        self.acquiring = True
        self.pvStart.put("Stop")
        # resets initial time value
        self.timer.mark()

    def stopCount(self):
        self.setCountStop()

    def setPresetValue(self, channel, val):
        """Dummy method"""
        pass

    def startCollectImage(self, rows=0, cols=0):
        super().startCollectImage('float32', rows, cols)

    def addRoi(self, roi):
        """ Insert a new roi
        roi: a tuple with begin and end: (begin,end)"""
        self.ROIS.append(roi)
Example #23
0
class Dxp(StandardDevice, ICountable):
    # CONSTRUCTOR OF DXP CLASS
    def __init__(self,
                 mnemonic,
                 output,
                 numberOfChannels=4,
                 numberOfRois=32,
                 pv=None,
                 dxpType="mca",
                 responseTimeout=15):
        """ Constructor
        responseTimeout : how much time to wait dxp answer
        """
        super().__init__(mnemonic)
        self.acquireChanged = Event()
        self.acquiring = False
        self.fileName = output

        # determines the start of counting
        self.pvDxpEraseStart = PV(pv + ":EraseStart.VAL")
        # determines mode of counting (Live Time, Real Time, ...)
        self.pvDxpPresetMode = PV(pv + ":PresetMode.VAL")

        self.pvDxpStop = PV(pv + ":StopAll.VAL")
        # store all channels
        self.pvDxpChannels = []
        # store ROIs
        self.pvDxpRois = []

        # store Acquire Time for each channel
        self.pvDxpAcquireTime = []

        for c in range(0, numberOfChannels):
            # store each channel
            self.pvDxpChannels.append(PV(pv + ":" + dxpType + str(c + 1)))
            # for each channel store the PV for AcquireTime
            self.pvDxpAcquireTime.append(
                PV(pv + ":" + dxpType + "%d.PLTM" % (c + 1)))
            self.pvDxpRois.append([])
            # storeing each ROI in your channel
            for r in range(0, numberOfRois):
                self.pvDxpRois[c].append(
                    PV(pv + ":" + dxpType + str(c + 1) + '.R' + str(r)))

        self.pvDxpAcquire = PV(pv + ":Acquiring")
        self.pvDxpAcquire.add_callback(self.statusChange)
        self.channels = numberOfChannels
        self.dxpType = dxpType
        self.rois = numberOfRois

        self.responseTimeout = responseTimeout
        self.timer = Timer(self.responseTimeout)

    def statusChange(self, value, **kw):
        """
        Helper callback used to wait for the end of the acquisition.
        """
        self.acquiring = value
        # threads waiting are awakened
        self.acquireChanged.set()

    def setCountTime(self, time):
        """
        Method to set the count time of a scaler device.

        Parameters
        ----------
        time : `float`
            Count time to set to scaler device .

        Returns
        -------
        out : None
        """
        for i in range(0, self.channels):
            self.pvDxpAcquireTime[i].put(time, wait=True)
        self.timer = Timer(time + self.responseTimeout)

    def getCountTime(self):
        return self.pvDxpTime.get()

    def setCountStop(self):
        self.pvDxpStop.put(1, wait=True)

    def getValueChannel(self, **kwargs):
        """Return intensity
        channel is on format mcaC.Rr, where C is  the channel and
        r is the ROI"""
        channel = kwargs['channel']
        c = int(channel[3]) - 1
        if (len(channel) > 4):
            r = int(channel[5])
            return self.pvDxpRois[c][r]
        else:
            self.saveSpectrum(c, **kwargs)
            return 1.0

    # save the spectrum intensity in a mca file
    def saveSpectrum(self, ch, **kwargs):
        fileName = self.fileName
        idx = 0
        if (fileName):
            spectrum = self.pvDxpChannels[ch].get(as_numpy=True)
            prefix = fileName.split('.')[0]
            while os.path.exists('%s_%s%d_%04d.mca' %
                                 (prefix, self.dxpType, ch, idx)):
                idx += 1
            fileName = '%s_%s%d_%04d.mca' % \
                       (prefix, self.dxpType, ch, idx)
        np.savetxt(fileName, spectrum, fmt='%f')

    def isCountRunning(self):
        return (self.pvDxpAcquire.get())

    def wait(self):
        """
        Blocks until the acquisition completes.
        """
        if self.acquiring is False:
            return

        self.acquireChanged.clear()
        # while acquiring and not time out waits
        # TODO: find a better way to do this
        while self.acquiring and self.timer.check():
            self.acquireChanged.wait(0.001)
            self.acquireChanged.clear()

        if self.timer.expired():
            raise RuntimeError('DXP is not answering')

    def canMonitor(self):
        """ Returns false indcating Dxp cannot be use as a counter monitor"""
        return False

    def canStopCount(self):
        """
        Returns true indicating that Dxp has a stop command.
        """
        return True

    def getValue(self, **kwargs):
        """
        This is a dummy method that always returns zero, which is part of the
        :class:`py4syn.epics.ICountable` interface. Dxp does not return
        a value while scanning. Instead, it stores a mca file with result .
        """
        if (kwargs):
            return self.getValueChannel(**kwargs)
        return self.getValueChannel()

    def isCounting(self):
        return self.acquiring

    def startCount(self):
        """ Starts acquiring an spectrum
        It's necessary to call setCounTime before"""

        if self.acquiring:
            raise RuntimeError('Already counting')

        self.acquiring = True
        self.pvDxpEraseStart.put(1)
        # resets initial time value
        self.timer.mark()

    def stopCount(self):
        self.setCountStop()

    def setPresetValue(self, channel, val):
        """Dummy method"""
        pass

    def close(self):
        """Stops an ongoing acquisition, if any, and puts the EPICS IOC in
        idle state."""
        self.pvDxpStop.put(1, wait=True)