516 lines
20 KiB
Python
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
|
|
|
|
|