Source code for contrast.detectors.Pilatus

from .Detector import (
    Detector, SoftwareLiveDetector, LiveDetector,
    TriggeredDetector, BurstDetector)
from ..environment import env
from ..recorders.Hdf5Recorder import Link
import os
import re
import time
import socket
import select

try:
    import tango
except ImportError:
    pass

BUF_SIZE = 1024
TIMEOUT = 20


[docs]class Pilatus2(Detector, SoftwareLiveDetector, TriggeredDetector, BurstDetector): """ Provides an interface to the Pilatus streaming manager, https://github.com/maxiv-science/pilatus-streamer https://gitlab.maxiv.lu.se/nanomax-beamline/streaming-receiver This class talks socket directly to the camserver. """ def __init__(self, hostname, name=None): BurstDetector.__init__(self) SoftwareLiveDetector.__init__(self) TriggeredDetector.__init__(self) # do this last so that initialize() can overwrite parent defaults: Detector.__init__(self, name=name) self._hdf_path = 'entry/measurement/Pilatus/data' self.hostname = hostname # this is also used for non-burst acquisition: self.burst_latency = .003 def initialize(self): self._initialize_socket() self.imgpath = '/lima_data/' def prepare(self, acqtime, dataid, n_starts): BurstDetector.prepare(self, acqtime, dataid, n_starts) self.arm_number = -1 if self.busy(): raise Exception('%s is busy!' % self.name) if (dataid is None) or (env.paths.directory is None): # no saving self.saving_file = '' else: # saving path = env.paths.directory fn = 'scan_%06d_%s.hdf5' % (dataid, self.name) self.saving_file = os.path.join(path, fn) if os.path.exists(self.saving_file): print('%s: this hdf5 file exists, I am raising an error now' % self.name) raise Exception('%s hdf5 file already exists' % self.name) self.exptime = self.acqtime self.expperiod = self.burst_latency + self.acqtime def arm(self): if self.busy(): raise Exception('%s is busy!' % self.name) self.arm_number += 1 if self.hw_trig and (self.burst_n == 1): # each image triggered self.nimages = self.hw_trig_n self._camserver_start( command='extmtrigger', filename=self.saving_file) elif self.hw_trig and (self.burst_n > 1): # triggered burst mode self.nimages = self.burst_n self._camserver_start( command='exttrigger', filename=self.saving_file) def start(self): if self.hw_trig: return if self.busy(): raise Exception('%s is busy!' % self.name) self.nimages = self.burst_n self._camserver_start(command='exposure', filename=self.saving_file) def stop(self): if not self.busy(): return self.sock.send(b'camcmd k\0') buf = '' while 'OK' not in buf: ready = select.select([self.sock], [], [], TIMEOUT) if ready[0]: buf += self.sock.recv(BUF_SIZE).decode(encoding='ascii') else: raise Exception('The camserver didnt accept the stop command. ' 'This happens when it is running above 10 Hz ' 'or so.') self._started = False def busy(self): if self.sock is None: return True if not self._started: return False ready = select.select([self.sock], [], [], 0.0) if ready[0]: response = self.sock.recv(BUF_SIZE).decode(encoding='ascii') if len(response) == 0: raise Exception( 'The socket connecting client to streamer is dead') if 'ERR' in response: print('Error! The %s acquisition didnt finish. Causing a ' 'ctrl-C...' % self.name) print('This should be done better, we could actually move ' 'on to the next line. But for now lets focus on not ' 'halting forever.') raise KeyboardInterrupt if response.startswith('7 OK'): self._started = False return False return True def read(self): if self.saving_file == '': return None else: return {'frames': Link(self.saving_file, self._hdf_path, universal=True), 'thumbs:': None, } def _initialize_socket(self): try: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.settimeout(TIMEOUT) self.sock.connect((self.hostname, 8888)) self._started = False except: self.sock = None def _camserver_start(self, filename='', command='exposure'): """ The command argument can be 'exposure', 'extmtrigger', 'extenable', 'exttrigger', see the Pilatus manual. """ if self.busy(): raise Exception('Already running!') allowed = ('exposure', 'extmtrigger', 'extenable', 'exttrigger') assert command in allowed res = self._query('%s %s' % (command, filename), timeout=TIMEOUT) if res is None or (not res.startswith('15 OK')) or ('ERR' in res): raise Exception('Error starting exposure') else: self._started = True def _query(self, command, timeout=TIMEOUT): if self.busy(): print('Detector measuring, better not...') return '' self._clear_buffer() self.sock.send(bytes(command + '\0', encoding='ascii')) ready = select.select([self.sock], [], [], TIMEOUT) if ready[0]: response = self.sock.recv(BUF_SIZE).decode(encoding='ascii') else: response = None return response def _clear_buffer(self): """ Checks that the connection is OK and clears the buffer. """ while True: ready = select.select([self.sock], [], [], 0) if ready[0]: try: dump = self.sock.recv(BUF_SIZE) except OSError: self._initialize_socket() return if len(dump) == 0: # only happens if the server has been dead print('something is strange - reinitializing the socket!') self._initialize_socket() else: break def _parse_response(self, data, pattern): match = re.compile(pattern).match(data) ret = match.groups(1)[0] if match else None return ret @property def energy(self): """ Operating energy """ res = self._query('setenergy', timeout=TIMEOUT) res = self._parse_response(res, '15 OK Energy setting: (.*) eV') energy = float(res) if res else None return energy @energy.setter def energy(self, value): res = self._query('setenergy %f' % value, timeout=TIMEOUT) if res is None or not res.startswith('15 OK'): raise Exception('Error setting energy') @property def exptime(self): res = self._query('exptime') res = self._parse_response( res, '15 OK Exposure time set to: (.*) sec.\x18') return float(res) @exptime.setter def exptime(self, value): res = self._query('exptime %f' % value) if res is None or not res.startswith('15 OK'): raise Exception('Error setting exptime') @property def expperiod(self): res = self._query('expperiod') res = self._parse_response( res, '15 OK Exposure period set to: (.*) sec\x18') return float(res) @expperiod.setter def expperiod(self, value): res = self._query('expperiod %f' % value) if res is None or not res.startswith('15 OK'): raise Exception('Error setting expperiod') @property def nimages(self): res = self._query('nimages') res = self._parse_response(res, '15 OK N images set to: ([0-9]+)') nimages = int(res) if res else None return nimages @nimages.setter def nimages(self, value): res = self._query('nimages %d' % value) if res is None or not res.startswith('15 OK'): raise Exception('Error setting nimages') @property def imgpath(self): res = self._query('imgpath') res = self._parse_response(res, '10 OK (.*)\x18') return res @imgpath.setter def imgpath(self, value): res = self._query('imgpath %s' % value) if res is None or not res.startswith('10 OK'): raise Exception('Error setting imgpath')
[docs]class Pilatus3(Detector, LiveDetector, TriggeredDetector, BurstDetector): """ Provides an interface to the MAX IV Pilatus Tango DS, https://gitlab.maxiv.lu.se/kits-maxiv/dev-pilatus https://github.com/maxiv-science/pilatus-streamer https://gitlab.maxiv.lu.se/nanomax-beamline/streaming-receiver There's no soft trigger on this detector. Also, the DS seems not to have HW triggered burst mode exposed. """ HDF_PATH = 'entry/measurement/Pilatus/data' def __init__(self, device_name, **kwargs): SoftwareLiveDetector.__init__(self) TriggeredDetector.__init__(self) BurstDetector.__init__(self) Detector.__init__(self, **kwargs) # arm waits for answer from streaming receiver, which # occasionally takes time, so increase timeout. self.proxy = tango.DeviceProxy(device_name) self.proxy.set_timeout_millis(10000) # this is also used for non-burst acquisition: self.burst_latency = .001 def start_live(self, acqtime=1.0): self.proxy.nTriggers = 10000 self.proxy.TriggerMode = 'INTERNAL' self.proxy.ExposureTime = acqtime self.proxy.FrameTime = acqtime + .1 self.proxy.Arm() def stop_live(self): self.proxy.Stop() while self.busy(): time.sleep(.1) def prepare(self, acqtime, dataid, n_starts): BurstDetector.prepare(self, acqtime, dataid, n_starts) acqtime = self.acqtime if self.busy(): raise Exception('%s is busy!' % self.name) if self.hw_trig and (self.burst_n > 1): raise ValueError( '%s Tango DS does not allow triggered burst mode.' % self.name) if (dataid is None) or (env.paths.directory is None): # no saving self.saving_file = '' else: # saving path = env.paths.directory fn = 'scan_%06d_%s.hdf5' % (dataid, self.name) self.saving_file = os.path.join(path, fn) if os.path.exists(self.saving_file): print('%s: this hdf5 file exists, I am raising an error now' % self.name) raise Exception('%s hdf5 file already exists' % self.name) self.proxy.Filename = self.saving_file # any any mode, self.proxy.ExposureTime = acqtime self.proxy.FrameTime = self.burst_latency + acqtime if self.hw_trig: # arm here for simple hw triggers to save time self.proxy.TriggerMode = 'EXTERNAL_MULTI' self.proxy.nTriggers = self.hw_trig_n self.proxy.Arm() else: # arm on each image or burst, later self.proxy.TriggerMode = 'INTERNAL' self.proxy.nTriggers = self.burst_n def arm(self): pass
[docs] def start(self): """ Start acquisition for any software triggered detectors. """ if not self.hw_trig: self.proxy.Arm()
def initialize(self): pass def stop(self): self.proxy.Stop() def busy(self): return not (self.proxy.State() == tango.DevState.ON) def read(self): if self.saving_file == '': return None else: return {'frames': Link(self.saving_file, self.HDF_PATH, universal=True), 'thumbs:': None, }