From d240dc1dcfcefa50bee80541c3be4fb899236048 Mon Sep 17 00:00:00 2001 From: Jerome Kieffer Date: Mon, 21 Oct 2013 16:35:17 +0200 Subject: [PATCH] new stuff --- plugins/Lima/pyFAI_lima_1d.py | 201 +++++++++++++++ plugins/Lima/{LimaFAI.ui => pyFAI_lima_1d.ui} | 9 +- pyFAI-src/hdf5.py | 243 ------------------ 3 files changed, 208 insertions(+), 245 deletions(-) create mode 100755 plugins/Lima/pyFAI_lima_1d.py rename plugins/Lima/{LimaFAI.ui => pyFAI_lima_1d.ui} (91%) delete mode 100644 pyFAI-src/hdf5.py diff --git a/plugins/Lima/pyFAI_lima_1d.py b/plugins/Lima/pyFAI_lima_1d.py new file mode 100755 index 00000000..d459739b --- /dev/null +++ b/plugins/Lima/pyFAI_lima_1d.py @@ -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 diff --git a/plugins/Lima/LimaFAI.ui b/plugins/Lima/pyFAI_lima_1d.ui similarity index 91% rename from plugins/Lima/LimaFAI.ui rename to plugins/Lima/pyFAI_lima_1d.ui index 98dd5931..a09b5436 100644 --- a/plugins/Lima/LimaFAI.ui +++ b/plugins/Lima/pyFAI_lima_1d.ui @@ -11,7 +11,7 @@ - PyFAI live 2D integration + PyFAI live 1D integration @@ -28,7 +28,7 @@ - + @@ -88,6 +88,11 @@ QGraphicsView
pyqtgraph
+ + PlotWidget + QGraphicsView +
pyqtgraph
+
diff --git a/pyFAI-src/hdf5.py b/pyFAI-src/hdf5.py deleted file mode 100644 index 6d65cc23..00000000 --- a/pyFAI-src/hdf5.py +++ /dev/null @@ -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 . -# - - - -__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