mirror of https://github.com/silx-kit/pyFAI.git
new stuff
This commit is contained in:
parent
fb5ab342bb
commit
d240dc1dcf
|
@ -0,0 +1,201 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
from __future__ import with_statement, print_function
|
||||
"""
|
||||
pyFAI_lima
|
||||
|
||||
A graphical tool (based on PyQt4) for performing azimuthal integration of images coming from a camera.
|
||||
No data are saved !
|
||||
|
||||
"""
|
||||
|
||||
__author__ = "Jerome Kieffer"
|
||||
__contact__ = "Jerome.Kieffer@ESRF.eu"
|
||||
__license__ = "GPLv3+"
|
||||
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
|
||||
__date__ = "11/10/2013"
|
||||
__satus__ = "development"
|
||||
|
||||
import sys
|
||||
import time
|
||||
import signal
|
||||
import threading
|
||||
import numpy
|
||||
import pyFAI.worker
|
||||
from pyFAI.io import HDF5Writer, h5py
|
||||
import pyopencl
|
||||
import os
|
||||
op = os.path
|
||||
import logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger("pyFAI")
|
||||
from PyQt4 import QtCore, QtGui, uic
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
import pyqtgraph as pg
|
||||
|
||||
|
||||
UIC = os.path.splitext(os.path.abspath(__file__))[0] + ".ui"
|
||||
window = None
|
||||
|
||||
|
||||
class DoubleView(QtGui.QWidget):
|
||||
def __init__(self, ip="192.168.5.19", fps=30, poni=None, json=None, writer=None):
|
||||
QtGui.QWidget.__init__(self)
|
||||
try:
|
||||
uic.loadUi(UIC, self)
|
||||
except AttributeError as error:
|
||||
logger.error("I looks like your installation suffers from this bug: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=697348")
|
||||
raise RuntimeError("Please upgrade your installation of PyQt (or apply the patch)")
|
||||
self.ip = str(ip)
|
||||
self.fps = float(fps)
|
||||
self.label_ip.setText(str(ip))
|
||||
self.label_fps.setText(str(fps))
|
||||
self.cam = self.iface = self.ctrl = self.acq = None
|
||||
self.cam = Basler.Camera(self.ip)
|
||||
self.iface = Basler.Interface(self.cam)
|
||||
self.ctrl = Core.CtControl(self.iface)
|
||||
self.is_playing = False
|
||||
self.connect(self.pushButton_play, SIGNAL("clicked()"), self.start_acq)
|
||||
self.connect(self.pushButton_stop, SIGNAL("clicked()"), self.stop_acq)
|
||||
self.last_frame = None
|
||||
self.last_plotItem = None
|
||||
self.last = time.time()
|
||||
if poni:
|
||||
worker = pyFAI.worker.Worker(ai=pyFAI.load(poni))
|
||||
elif json:
|
||||
worker = pyFAI.worker.Worker()
|
||||
worker.setJsonConfig(json)
|
||||
else:
|
||||
worker = None
|
||||
self.processLink = LinkPyFAI(worker, writer)
|
||||
self.extMgr = self.ctrl.externalOperation()
|
||||
self.myOp = self.extMgr.addOp(Core.USER_LINK_TASK, "pyFAILink", 0)
|
||||
self.myOp.setLinkTask(self.processLink)
|
||||
|
||||
self.callback = StartAcqCallback(self.ctrl, self.processLink)
|
||||
self.myOp.registerCallback(self.callback)
|
||||
self.timer = QtCore.QTimer()
|
||||
self.connect(self.timer, SIGNAL("timeout()"), self.update_img)
|
||||
self.writer = writer
|
||||
|
||||
def start_acq(self):
|
||||
if self.is_playing: return
|
||||
self.is_playing = True
|
||||
self.acq = self.ctrl.acquisition()
|
||||
self.acq.setAcqNbFrames(0)
|
||||
self.acq.setAcqExpoTime(1.0 / self.fps)
|
||||
self.ctrl.prepareAcq()
|
||||
self.ctrl.startAcq()
|
||||
while self.ctrl.getStatus().ImageCounters.LastImageReady < 1:
|
||||
time.sleep(0.1)
|
||||
self.last_frame = self.ctrl.getStatus().ImageCounters.LastImageReady
|
||||
raw_img = self.ctrl.ReadBaseImage().buffer
|
||||
fai_img = self.ctrl.ReadImage().buffer
|
||||
self.RawImg.setImage(raw_img.T)#, levels=[0, 4096])#, autoLevels=False, autoRange=False)
|
||||
self.last_plotItem = pg.PlotDataItem(fai_img[:, 0], fai_img[:, 1])
|
||||
self.FaiPlot.addItem(fai_img.T)#, levels=[0, 4096])#, autoLevels=False, autoRange=False)
|
||||
self.last = time.time()
|
||||
self.timer.start(1000.0 / self.fps)
|
||||
|
||||
def stop_acq(self):
|
||||
if self.is_playing:
|
||||
self.is_playing = False
|
||||
self.ctrl.stopAcq()
|
||||
self.timer.stop()
|
||||
|
||||
def update_img(self):
|
||||
last_frame = self.ctrl.getStatus().ImageCounters.LastImageReady
|
||||
if last_frame == self.last_frame:
|
||||
return
|
||||
if self.is_playing:
|
||||
raw_img = self.ctrl.ReadBaseImage().buffer
|
||||
fai_img = self.ctrl.ReadImage().buffer
|
||||
self.RawImg.setImage(raw_img.T)#, levels=[0, 4096])#, autoLevels=False, autoRange=False)
|
||||
# with self._sem:
|
||||
plotItem = pg.PlotDataItem(fai_img[:, 0], fai_img[:, 1])
|
||||
self.FaiPlot.removeItem(self.last_plotItem)
|
||||
self.FaiPlot.addItem(self.last_plotItem)#, levels=[0, 4096])#, autoLevels=False, autoRange=False)
|
||||
self.last_plotItem = plotItem
|
||||
# self.FaiImg.setImage(fai_img.T)#, levels=[0, 4096])#, autoLevels=False, autoRange=False)
|
||||
print("Measured display speed: %5.2f fps" % (1.0 / (time.time() - self.last)))
|
||||
self.last = time.time()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from optparse import OptionParser
|
||||
usage = "usage: %prog [options] "
|
||||
version = "%prog " + pyFAI.version
|
||||
description = """
|
||||
pyFAI-lima is a graphical interface (based on Python/Qt4) to perform azimuthal integration
|
||||
on a set of files grabbed from a Basler camera using LImA."""
|
||||
epilog = """ """
|
||||
parser = OptionParser(usage=usage, version=version, description=description, epilog=epilog)
|
||||
parser.add_option("-v", "--verbose",
|
||||
action="store_true", dest="verbose", default=False,
|
||||
help="switch to verbose/debug mode")
|
||||
parser.add_option("-p", "--poni",
|
||||
dest="poni", default=None,
|
||||
help="PONI file containing the setup")
|
||||
parser.add_option("-j", "--json",
|
||||
dest="json", default=None,
|
||||
help="json file containing the setup")
|
||||
parser.add_option("-f", "--fps",
|
||||
dest="fps", default="30",
|
||||
help="Number of frames per seconds")
|
||||
parser.add_option("-i", "--ip",
|
||||
dest="ip", default="192.168.5.19",
|
||||
help="IP address of the Basler camera")
|
||||
parser.add_option("-l", "--lima",
|
||||
dest="lima", default=None,
|
||||
help="Base installation of LImA")
|
||||
parser.add_option("-s", "--scan",
|
||||
dest="scan", default=None,
|
||||
help="Size of scan of the fastest motor")
|
||||
|
||||
parser.add_option("--no-gui",
|
||||
dest="gui", default=True, action="store_false",
|
||||
help="Process the dataset without showing the user interface.")
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
if len(args) == 1:
|
||||
hurl = args[0]
|
||||
if hurl.startswith("hdf5:"):
|
||||
hurl = hurl[5:]
|
||||
if ":" in hurl:
|
||||
hsplit = hurl.split(":")
|
||||
hdfpath = hsplit[-1]
|
||||
hdffile = ":".join(hsplit[:-1]) #special windows
|
||||
else:
|
||||
hdfpath = "test_LImA+pyFAI"
|
||||
hdffile = hurl
|
||||
writer = HDF5Writer(hdffile, hdfpath, options.scan)
|
||||
elif len(args) > 1 :
|
||||
logger.error("Specify the HDF5 output file like hdf5:///home/user/filename.h5:/path/to/group")
|
||||
sys.exit(1)
|
||||
else:
|
||||
writer = None
|
||||
|
||||
if options.verbose:
|
||||
logger.info("setLevel: debug")
|
||||
logger.setLevel(logging.DEBUG)
|
||||
if options.lima:
|
||||
sys.path.insert(0, options.lima)
|
||||
try:
|
||||
from Lima import Core, Basler
|
||||
|
||||
except ImportError:
|
||||
print("Is the PYTHONPATH correctly setup? I did not manage to import Lima")
|
||||
sys.exit(1)
|
||||
from limaFAI import LinkPyFAI, StartAcqCallback
|
||||
if options.gui:
|
||||
app = QtGui.QApplication([])
|
||||
window = DoubleView(ip=options.ip, fps=options.fps, writer=writer)
|
||||
#window.set_input_data(args)
|
||||
window.show()
|
||||
sys.exit(app.exec_())
|
||||
else:
|
||||
raise Exception("No sense!")
|
||||
pass
|
|
@ -11,7 +11,7 @@
|
|||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>PyFAI live 2D integration</string>
|
||||
<string>PyFAI live 1D integration</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
|
@ -28,7 +28,7 @@
|
|||
<widget class="ImageView" name="RawImg"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ImageView" name="FaiImg"/>
|
||||
<widget class="PlotWidget" name="FaiPlot"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
|
@ -88,6 +88,11 @@
|
|||
<extends>QGraphicsView</extends>
|
||||
<header>pyqtgraph</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>PlotWidget</class>
|
||||
<extends>QGraphicsView</extends>
|
||||
<header>pyqtgraph</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
|
@ -1,243 +0,0 @@
|
|||
# !/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Project: Azimuthal integration
|
||||
# https://github.com/kif
|
||||
#
|
||||
#
|
||||
# Copyright (C) European Synchrotron Radiation Facility, Grenoble, France
|
||||
#
|
||||
# Principal author: Jérôme Kieffer (Jerome.Kieffer@ESRF.eu)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
|
||||
|
||||
__author__ = "Jerome Kieffer"
|
||||
__contact__ = "Jerome.Kieffer@ESRF.eu"
|
||||
__license__ = "GPLv3+"
|
||||
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
|
||||
__date__ = "15/10/2013"
|
||||
__status__ = "beta"
|
||||
__docformat__ = 'restructuredtext'
|
||||
__doc__ = """
|
||||
Stand-alone module which tries to offer interface to HDF5 via H5Py.
|
||||
|
||||
Can be imported without h5py but completely useless.
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
import logging
|
||||
logger = logging.getLogger("pyFAI.hdf5")
|
||||
import types
|
||||
import numpy
|
||||
import posixpath
|
||||
import json
|
||||
#import threading
|
||||
try:
|
||||
import h5py
|
||||
except ImportError:
|
||||
h5py = None
|
||||
logger.debug("h5py is missing")
|
||||
|
||||
def getIsoTime(forceTime=None):
|
||||
"""
|
||||
@param forceTime: enforce a given time (current by default)
|
||||
@type forceTime: float
|
||||
@return: the current time as an ISO8601 string
|
||||
@rtype: string
|
||||
"""
|
||||
if forceTime is None:
|
||||
forceTime = time.time()
|
||||
localtime = time.localtime(forceTime)
|
||||
gmtime = time.gmtime(forceTime)
|
||||
tz_h = localtime.tm_hour - gmtime.tm_hour
|
||||
tz_m = localtime.tm_min - gmtime.tm_min
|
||||
return "%s%+03i:%02i" % (time.strftime("%Y-%m-%dT%H:%M:%S", localtime), tz_h, tz_m)
|
||||
|
||||
class HDF5Writer(object):
|
||||
"""
|
||||
Class allowing to write HDF5 Files.
|
||||
|
||||
"""
|
||||
CONFIG = "pyFAI"
|
||||
DATA = "data"
|
||||
def __init__(self, filename, hpath="data", fast_scan_width=None):
|
||||
"""
|
||||
Constructor of an HDF5 writer:
|
||||
|
||||
@param filename: name of the file
|
||||
@param hpath: name of the group: it will contain data (2-4D dataset), [tth|q|r] and pyFAI, group containing the configuration
|
||||
@param fast_scan_width: set it to define the width of
|
||||
"""
|
||||
self.filename = filename
|
||||
self.hpath = hpath
|
||||
self.fast_scan_width = None
|
||||
if fast_scan_width is not None:
|
||||
try:
|
||||
self.fast_scan_width = int(fast_scan_width)
|
||||
except:
|
||||
pass
|
||||
self.hdf5 = None
|
||||
self.group = None
|
||||
self.dataset = None
|
||||
self.pyFAI_grp = None
|
||||
self.radial_values = None
|
||||
self.azimuthal_values = None
|
||||
self.chunk = None
|
||||
self.shape = None
|
||||
self.ndim = None
|
||||
self._sem = threading.Semaphore()
|
||||
self.config = {}
|
||||
|
||||
def __repr__(self):
|
||||
return "HDF5 writer on file %s:%s %sinitialized" % (self.filename, self.hpath, "" if self._initialized else "un")
|
||||
|
||||
def init(self, config=None, lima_cfg=None):
|
||||
"""
|
||||
Initializes the HDF5 file for writing
|
||||
@param config: the configuration of the worker as a dictionary
|
||||
"""
|
||||
with self._sem:
|
||||
if not config:
|
||||
config = self.config
|
||||
self.config = config
|
||||
open("config.json", "w").write(json.dumps(config, indent=4))
|
||||
config["nbpt_rad"] = config.get("nbpt_rad", 1000)
|
||||
dirname = os.path.dirname(self.filename)
|
||||
if not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
if h5py:
|
||||
try:
|
||||
self.hdf5 = h5py.File(self.filename)
|
||||
except IOError: #typically a corrupted HDF5 file !
|
||||
os.unlink(self.filename)
|
||||
self.hdf5 = h5py.File(self.filename)
|
||||
else:
|
||||
logger.error("No h5py library, no chance")
|
||||
raise RuntimeError("No h5py library, no chance")
|
||||
self.group = self.hdf5.require_group(self.hpath)
|
||||
self.group.attrs["NX_class"] = "NXentry"
|
||||
self.pyFAI_grp = self.hdf5.require_group(posixpath.join(self.hpath, self.CONFIG))
|
||||
self.pyFAI_grp.attrs["desc"] = "PyFAI worker configuration"
|
||||
for key, value in config.items():
|
||||
if value is None:
|
||||
continue
|
||||
try:
|
||||
self.pyFAI_grp[key] = value
|
||||
except:
|
||||
print("Unable to set %s: %s" % (key, value))
|
||||
self.close()
|
||||
sys.exit(1)
|
||||
rad_name, rad_unit = str(config.get("unit", "2th_deg")).split("_", 1)
|
||||
self.radial_values = self.group.require_dataset(rad_name, (config["nbpt_rad"],), numpy.float32)
|
||||
if config.get("nbpt_azim", 0) > 1:
|
||||
self.azimuthal_values = self.group.require_dataset("chi", (config["nbpt_azim"],), numpy.float32)
|
||||
self.azimuthal_values.attrs["unit"] = "deg"
|
||||
self.radial_values.attrs["interpretation"] = "scalar"
|
||||
self.radial_values.attrs["long name"] = "Azimuthal angle"
|
||||
|
||||
self.radial_values.attrs["unit"] = rad_unit
|
||||
self.radial_values.attrs["interpretation"] = "scalar"
|
||||
self.radial_values.attrs["long name"] = "diffraction radial direction"
|
||||
if self.fast_scan_width:
|
||||
self.fast_motor = self.group.require_dataset("fast", (self.fast_scan_width,) , numpy.float32)
|
||||
self.fast_motor.attrs["long name"] = "Fast motor position"
|
||||
self.fast_motor.attrs["interpretation"] = "scalar"
|
||||
self.fast_motor.attrs["axis"] = "1"
|
||||
self.radial_values.attrs["axis"] = "2"
|
||||
if self.azimuthal_values is not None:
|
||||
chunk = 1, self.fast_scan_width, config["nbpt_azim"], config["nbpt_rad"]
|
||||
self.ndim = 4
|
||||
self.azimuthal_values.attrs["axis"] = "3"
|
||||
else:
|
||||
chunk = 1, self.fast_scan_width, config["nbpt_rad"]
|
||||
self.ndim = 3
|
||||
else:
|
||||
self.radial_values.attrs["axis"] = "1"
|
||||
if self.azimuthal_values is not None:
|
||||
chunk = 1, config["nbpt_azim"], config["nbpt_rad"]
|
||||
self.ndim = 3
|
||||
self.azimuthal_values.attrs["axis"] = "2"
|
||||
else:
|
||||
chunk = 1, config["nbpt_rad"]
|
||||
self.ndim = 2
|
||||
|
||||
if self.DATA in self.group:
|
||||
del self.group[self.DATA]
|
||||
self.dataset = self.group.require_dataset(self.DATA, chunk, dtype=numpy.float32, chunks=chunk,
|
||||
maxshape=(None,) + chunk[1:])
|
||||
if config.get("nbpt_azim", 0) > 1:
|
||||
self.dataset.attrs["interpretation"] = "image"
|
||||
else:
|
||||
self.dataset.attrs["interpretation"] = "spectrum"
|
||||
self.dataset.attrs["signal"] = "1"
|
||||
self.chunk = chunk
|
||||
self.shape = chunk
|
||||
name = "Mapping " if self.fast_scan_width else "Scanning "
|
||||
name += "2D" if config.get("nbpt_azim", 0) > 1 else "1D"
|
||||
name += " experiment"
|
||||
self.group["title"] = name
|
||||
self.group["program"] = "PyFAI"
|
||||
self.group["start_time"] = getIsoTime()
|
||||
|
||||
|
||||
|
||||
def flush(self, radial=None, azimuthal=None):
|
||||
"""
|
||||
Update some data like axis units and so on.
|
||||
|
||||
@param radial: position in radial direction
|
||||
@param azimuthal: position in azimuthal direction
|
||||
"""
|
||||
if not self.hdf5:
|
||||
raise RuntimeError('No opened file')
|
||||
if radial is not None:
|
||||
if radial.shape == self.radial_values.shape:
|
||||
self.radial_values[:] = radial
|
||||
else:
|
||||
logger.warning("Unable to assign radial axis position")
|
||||
if azimuthal is not None:
|
||||
if azimuthal.shape == self.azimuthal_values.shape:
|
||||
self.azimuthal_values[:] = azimuthal
|
||||
else:
|
||||
logger.warning("Unable to assign azimuthal axis position")
|
||||
self.hdf5.flush()
|
||||
|
||||
def close(self):
|
||||
if self.hdf5:
|
||||
self.flush()
|
||||
self.hdf5.close()
|
||||
self.hdf5 = None
|
||||
|
||||
def write(self, data, index=0):
|
||||
"""
|
||||
Minimalistic method to limit the overhead.
|
||||
"""
|
||||
with self._sem:
|
||||
if self.dataset is None:
|
||||
logger.warning("Writer not initialized !")
|
||||
return
|
||||
if self.fast_scan_width:
|
||||
index0, index1 = (index // self.fast_scan_width, index % self.fast_scan_width)
|
||||
if index0 >= self.dataset.shape[0]:
|
||||
self.dataset.resize(index0 + 1, axis=0)
|
||||
self.dataset[index0, index1] = data
|
||||
else:
|
||||
if index >= self.dataset.shape[0]:
|
||||
self.dataset.resize(index + 1, axis=0)
|
||||
self.dataset[index] = data
|
Loading…
Reference in New Issue