Files
x05la/script/Devices/Eiger.py
gac-x05la 2a65e100ea Sessions
2021-05-20 15:42:59 +02:00

516 lines
20 KiB
Python

"""
class DEigerClient provides an interface to the EIGER API
Author: Volker Pilipp
Contact: support@dectris.com
Version: 1.0
Date: 13/11/2014
Copyright See General Terms and Conditions (GTC) on http://www.dectris.com
"""
import base64
import os.path
import httplib
import json
import re
import sys
import socket
import fnmatch
Version = '1.6.0'
class DEigerClient(object):
"""
class DEigerClient provides a low level interface to the EIGER API
"""
def __init__(self, host = '127.0.0.1', port = 80, verbose = False, urlPrefix = None, user = None):
"""
Create a client object to talk to the EIGER API.
Args:
host: hostname of the detector computer
port: port usually 80 (http)
verbose: bool value
urlPrefix: String prepended to the urls. Should be None.
user: "username:password". Should be None.
"""
super(DEigerClient,self).__init__()
self._host = host
self._port = port
self._version = Version
self._verbose = verbose
self._urlPrefix = ""
self._user = None
self._connectionTimeout = 900
self._connection = httplib.HTTPConnection(self._host,self._port, timeout = self._connectionTimeout)
self.setUrlPrefix(urlPrefix)
self.setUser(user)
def setVerbose(self,verbose):
""" Switch verbose mode on and off.
Args:
verbose: bool value
"""
self._verbose = bool(verbose)
def setConnectionTimeout(self, timeout):
"""
If DEigerClient has not received an reply from EIGER after
timeout seconds, the request is aborted. timeout should be at
least as long as the triggering command takes.
Args:
timeout timeout in seconds
"""
self._connectionTimeout = timeout
def setUrlPrefix(self, urlPrefix):
"""Set url prefix, which is the string that is prepended to the
urls. There is usually no need to call the command explicitly.
Args:
urlPrefix: String
"""
if urlPrefix is None:
self._urlPrefix = ""
else:
self._urlPrefix = str(urlPrefix)
if len(self._urlPrefix) > 0 and self._urlPrefix[-1] != "/":
self._urlPrefix += "/"
def setUser(self, user):
"""
Set username and password for basic authentication.
There is usually no need to call the command explicitly.
Args:
user: String of the form username:password
"""
if user is None:
self._user = None
else:
self._user = base64.encodestring(user).replace('\n', '')
def version(self,module = 'detector'):
"""Get version of a api module (i.e. 'detector', 'filewriter')
Args:
module: 'detector' or 'filewriter'
"""
return self._getRequest(url = '/{0}{1}/api/version/'.format(self._urlPrefix,module))
def listDetectorConfigParams(self):
"""Get list of all detector configuration parameters (param arg of configuration() and setConfiguration()).
Convenience function, that does detectorConfig(param = 'keys')
Returns:
List of parameters.
"""
return self.detectorConfig('keys')
def detectorConfig(self,param = None, dataType = None):
"""Get detector configuration parameter
Args:
param: query the configuration parameter param, if None get full configuration, if 'keys' get all configuration parameters.
dataType: None (= 'native'), 'native' ( return native python object) or 'tif' (return tif data).
Returns:
If param is None get configuration, if param is 'keys' return list of all parameters, else return the value of
the parameter. If dataType is 'native' a dictionary is returned that may contain the keys: value, min, max,
allowed_values, unit, value_type and access_mode. If dataType is 'tif', tiff formated data is returned as a python
string.
"""
return self._getRequest(self._url('detector','config',param),dataType)
def setDetectorConfig(self, param, value, dataType = None):
"""
Set detector configuration parameter param.
Args:
param: Parameter
value: Value to set. If dataType is 'tif' value may be a string containing the tiff data or
a file object pointing to a tiff file.
dataType: None, 'native' or 'tif'. If None, the data type is auto determined. If 'native' value
may be a native python object (e.g. int, float, str), if 'tif' value shell contain a
tif file (python string or file object to tif file).
Returns:
List of changed parameters.
"""
return self._putRequest(self._url('detector','config',param), dataType, value)
def setDetectorConfigMultiple(self,*params):
"""
Convenience function that calls setDetectorConfig(param,value,dataType = None) for
every pair param, value in *params.
Args:
*params: List of successive params of the form param0, value0, param1, value1, ...
The parameters are set in the same order they appear in *params.
Returns:
List of changed parameters.
"""
changeList = []
p = None
for x in params:
if p is None:
p = x
else:
data = x
changeList += self.setDetectorConfig(param = p, value = data, dataType = None)
p = None
return list(set(changeList))
def listDetectorCommands(self):
"""
Get list of all commands that may be sent to Eiger via command().
Returns:
List of commands
"""
return self._getRequest(self._url('detector','command','keys'))
def sendDetectorCommand(self, command, parameter = None):
"""
Send command to Eiger. The list of all available commands is obtained via listCommands().
Args:
command: Detector command
parameter: Call command with parameter. If command = "trigger" a float parameter may be passed
Returns:
The commands 'arm' and 'trigger' return a dictionary containing 'sequence id'.
"""
return self._putRequest(self._url('detector','command',command), dataType = 'native', data = parameter)
def detectorStatus(self, param = 'keys'):
"""Get detector status information
Args:
param: query the status parameter param, if 'keys' get all status parameters.
Returns:
If param is None get configuration, if param is 'keys' return list of all parameters, else return dictionary
that may contain the keys: value, value_type, unit, time, state, critical_limits, critical_values
"""
return self._getRequest(self._url('detector','status',parameter = param))
def fileWriterConfig(self,param = 'keys'):
"""Get filewriter configuration parameter
Args:
param: query the configuration parameter param, if 'keys' get all configuration parameters.
Returns:
If param is None get configuration, if param is 'keys' return list of all parameters, else return dictionary
that may contain the keys: value, min, max, allowed_values, unit, value_type and access_mode
"""
return self._getRequest(self._url('filewriter','config',parameter = param))
def setFileWriterConfig(self,param,value):
"""
Set file writer configuration parameter param.
Args:
param: parameter
value: value to set
Returns:
List of changed parameters.
"""
return self._putRequest(self._url('filewriter','config',parameter = param), dataType = 'native', data = value)
def sendFileWriterCommand(self, command):
"""
Send filewriter command to Eiger.
Args:
command: Command to send (up to now only "clear")
Returns:
Empty string
"""
return self._putRequest(self._url("filewriter","command",parameter = command), dataType = "native")
def fileWriterStatus(self,param = 'keys'):
"""Get filewriter status information
Args:
param: query the status parameter param, if 'keys' get all status parameters.
Returns:
If param is None get configuration, if param is 'keys' return list of all parameters, else return dictionary
that may contain the keys: value, value_type, unit, time, state, critical_limits, critical_values
"""
return self._getRequest(self._url('filewriter','status',parameter = param))
def fileWriterFiles(self, filename = None, method = 'GET'):
"""
Obtain file from detector.
Args:
filename: Name of file on the detector side. If None return list of available files
method: Eiger 'GET' (get the content of the file) or 'DELETE' (delete file from server)
Returns:
List of available files if 'filename' is None,
else if method is 'GET' the content of the file.
"""
if method == 'GET':
if filename is None:
return self._getRequest(self._url('filewriter','files'))
else:
return self._getRequest(url = '/{0}data/{1}'.format(self._urlPrefix, filename), dataType = 'hdf5')
elif method == 'DELETE':
return self._delRequest(url = '/{0}data/{1}'.format(self._urlPrefix,filename))
else:
raise RuntimeError('Unknown method {0}'.format(method))
def fileWriterSave(self,filename,targetDir,regex = False):
"""
Saves filename in targetDir. If regex is True, filename is considered to be a regular expression.
Save all files that match filename
Args:
filename: Name of source file, evtl. regular expression
targetDir: Directory, where to store the files
"""
if regex:
pattern = re.compile(filename)
[ self.fileWriterSave(f,targetDir) for f in self.fileWriterFiles() if pattern.match(f) ]
elif any([ c in filename for c in ['*','?','[',']'] ] ):
# for f in self.fileWriterFiles():
# self._log('DEBUG ', f, ' ', fnmatch.fnmatch(f,filename))
[ self.fileWriterSave(f,targetDir) for f in self.fileWriterFiles() if fnmatch.fnmatch(f,filename) ]
else:
targetPath = os.path.join(targetDir,filename)
with open(targetPath,'wb') as targetFile:
self._log('Writing ', targetPath)
self._getRequest(url = '/{0}data/{1}'.format(self._urlPrefix, filename), dataType = 'hdf5',fileId = targetFile)
# targetFile.write(self.fileWriterFiles(filename))
assert os.access(targetPath,os.R_OK)
return
def monitorConfig(self,param = 'keys'):
"""Get monitor configuration parameter
Args:
param: query the configuration parameter param, if 'keys' get all configuration parameters.
Returns:
If param is 'keys' return list of all parameters, else return dictionary
that may contain the keys: value, min, max, allowed_values, unit, value_type and access_mode
"""
return self._getRequest(self._url('monitor','config',parameter = param))
def setMonitorConfig(self,param,value):
"""
Set monitor configuration parameter param.
Args:
param: parameter
value: value to set
Returns:
List of changed parameters.
"""
return self._putRequest(self._url('monitor','config',parameter = param), dataType = 'native', data = value)
def monitorImages(self, param = None):
"""
Obtain file from detector.
Args:
param: Either None (return list of available frames) or "monitor" (return latest frame),
"next" (next image from buffer) or tuple(sequence id, image id) (return specific image)
Returns:
List of available frames (param = None) or tiff content of image file (param = "next", "monitor", (seqId,imgId))
"""
if param is None:
return self._getRequest(self._url('monitor','images',parameter = None) )
elif param == "next":
return self._getRequest(self._url('monitor',"images", parameter = "next"), dataType = "tif")
elif param == "monitor":
return self._getRequest(self._url('monitor','images',parameter = "monitor"), dataType = "tif")
else:
try:
seqId = int(param[0])
imgId = int(param[1])
return self._getRequest(self._url('monitor',"images", parameter = "{0}/{1}".format(seqId,imgId) ), dataType = 'tif')
except (TypeError, ValueError):
pass
raise RuntimeError('Invalid parameter {0}'.format(param))
def monitorSave(self, param, path):
"""
Save frame to path as tiff file.
Args:
param: same as monitorImages()
Returns:
None
"""
data = None
if param in ["next","monitor"]:
data = self.monitorImages(param)
else :
try:
int(param[0])
int(param[1])
data = self.monitorImages(param)
except (TypeError, ValueError):
pass
if data is None:
raise RuntimeError('Invalid parameter {0}'.format(param))
else:
with open(path,'wb') as f:
self._log('Writing ', path)
f.write(data)
assert os.access(path,os.R_OK)
return
def monitorStatus(self, param):
"""Get monitor status information
Args:
param: query the status parameter param, if 'keys' get all status parameters.
Returns:
Dictionary that may contain the keys: value, value_type, unit, time, state,
critical_limits, critical_values
"""
return self._getRequest(self._url('monitor','status',parameter = param))
def sendMonitorCommand(self, command):
"""
Send monitor command to Eiger.
Args:
command: Command to send (up to now only "clear")
Returns:
Empty string
"""
return self._putRequest(self._url("monitor","command",parameter = command), dataType = "native")
def streamConfig(self,param = 'keys'):
"""Get stream configuration parameter
Args:
param: query the configuration parameter param, if 'keys' get all configuration parameters.
Returns:
If param is 'keys' return list of all parameters, else return dictionary
that may contain the keys: value, min, max, allowed_values, unit, value_type and access_mode
"""
return self._getRequest(self._url('stream','config',parameter = param))
def setStreamConfig(self,param,value):
"""
Set stream configuration parameter param.
Args:
param: parameter
value: value to set
Returns:
List of changed parameters.
"""
return self._putRequest(self._url('stream','config',parameter = param), dataType = 'native', data = value)
def streamStatus(self, param):
"""Get stream status information
Args:
param: query the status parameter param, if 'keys' get all status parameters.
Returns:
Dictionary that may contain the keys: value, value_type, unit, time, state,
critical_limits, critical_values
"""
return self._getRequest(self._url('stream','status',parameter = param))
#
#
# Private Methods
#
#
def _log(self,*args):
if self._verbose:
print ' '.join([ str(elem) for elem in args ])
def _url(self,module,task,parameter = None):
url = "/{0}{1}/api/{2}/{3}/".format(self._urlPrefix, module, self._version, task)
if not parameter is None:
url += '{0}'.format(parameter)
return url
def _getRequest(self,url,dataType = 'native', fileId = None):
if dataType is None:
dataType = 'native'
if dataType == 'native':
mimeType = 'application/json; charset=utf-8'
elif dataType == 'tif':
mimeType = 'application/tiff'
elif dataType == 'hdf5':
mimeType = 'application/hdf5'
return self._request(url,'GET',mimeType, fileId = fileId)
def _putRequest(self,url,dataType,data = None):
data, mimeType = self._prepareData(data,dataType)
return self._request(url,'PUT',mimeType, data)
def _delRequest(self,url):
self._request(url,'DELETE',mimeType = None)
return None
def _request(self, url, method, mimeType, data = None, fileId = None):
if data is None:
body = ''
else:
body = data
headers = {}
if method == 'GET':
headers['Accept'] = mimeType
elif method == 'PUT':
headers['Content-type'] = mimeType
if not self._user is None:
headers["Authorization"] = "Basic {0}".format(self._user)
self._log('sending request to {0}'.format(url))
numberOfTries = 0
response = None
while response is None:
try:
self._connection.request(method,url, body = data, headers = headers)
response = self._connection.getresponse()
except Exception as e:
numberOfTries += 1
if numberOfTries == 50:
self._log("Terminate after {0} tries\n".format(numberOfTries))
raise e
self._log("Failed to connect to host. Retrying\n")
self._connection = httplib.HTTPConnection(self._host,self._port, timeout = self._connectionTimeout)
continue
status = response.status
reason = response.reason
if fileId is None:
data = response.read()
else:
bufferSize = 8*1024
while True:
data = response.read(bufferSize)
if len(data) > 0:
fileId.write(data)
else:
break
mimeType = response.getheader('content-type','text/plain')
self._log('Return status: ', status, reason)
if not response.status in range(200,300):
raise RuntimeError((reason,data))
if 'json' in mimeType:
return json.loads(data)
else:
return data
def _prepareData(self,data, dataType):
if data is None:
return '', 'text/html'
if dataType != 'native':
if type(data) == file:
data = data.read()
if dataType is None:
mimeType = self._guessMimeType(data)
if not mimeType is None:
return data, mimeType
elif dataType == 'tif':
return data, 'application/tiff'
mimeType = 'application/json; charset=utf-8'
return json.dumps({'value':data}), mimeType
def _guessMimeType(self,data):
if type(data) == str:
if data.startswith('\x49\x49\x2A\x00') or data.startswith('\x4D\x4D\x00\x2A'):
self._log('Determined mimetype: tiff')
return 'application/tiff'
if data.startswith('\x89\x48\x44\x46\x0d\x0a\x1a\x0a'):
self._log('Determined mimetype: hdf5')
return 'application/hdf5'
return None