summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPicca Frédéric-Emmanuel <picca@synchrotron-soleil.fr>2019-12-23 13:45:09 +0100
committerPicca Frédéric-Emmanuel <picca@synchrotron-soleil.fr>2019-12-23 13:45:09 +0100
commit5d647cf9a6159afd2933da594b9c79ad93d3cd9b (patch)
tree2571025a602f68fc8933b01104dc712d41f84034
parent654a6ac93513c3cc1ef97cacd782ff674c6f4559 (diff)
New upstream version 0.12.0~b0+dfsg
-rw-r--r--CHANGELOG.rst68
-rw-r--r--PKG-INFO3
-rwxr-xr-xbuild-deb.sh97
-rw-r--r--doc/source/ext/snapshotqt_directive.py20
-rw-r--r--doc/source/install.rst2
-rw-r--r--doc/source/modules/opencl/convolution.rst2
-rw-r--r--doc/source/sample_code/img/compareImages.pngbin0 -> 33398 bytes
-rw-r--r--doc/source/sample_code/img/compositeline.pngbin0 -> 23150 bytes
-rw-r--r--doc/source/sample_code/img/dropZones.pngbin0 -> 8182 bytes
-rw-r--r--doc/source/sample_code/img/exampleBaseline.pngbin0 -> 79266 bytes
-rw-r--r--doc/source/sample_code/img/findContours.pngbin0 -> 390630 bytes
-rw-r--r--doc/source/sample_code/img/plotCurveLegendWidget.pngbin0 -> 105797 bytes
-rw-r--r--doc/source/sample_code/img/plotStats.pngbin0 -> 52815 bytes
-rw-r--r--doc/source/sample_code/img/scatterview.pngbin0 -> 73884 bytes
-rw-r--r--doc/source/sample_code/img/syncPlotLocation.pngbin0 -> 178464 bytes
-rw-r--r--doc/source/sample_code/index.rst210
-rw-r--r--examples/compositeline.py80
-rw-r--r--examples/exampleBaseline.py164
-rw-r--r--examples/findContours.py4
-rwxr-xr-xexamples/hdf5widget.py9
-rwxr-xr-xexamples/imageview.py7
-rw-r--r--examples/plotInteractiveImageROI.py9
-rwxr-xr-xexamples/scatterview.py99
-rwxr-xr-xexamples/simplewidget.py64
-rw-r--r--package/debian10/changelog141
-rw-r--r--package/debian10/compat1
-rw-r--r--package/debian10/control172
-rw-r--r--package/debian10/gbp.conf (renamed from package/debian8/gbp.conf)0
-rw-r--r--package/debian10/patches/0002-use-the-system-mathjax-privacy-breach.patch25
-rw-r--r--package/debian10/patches/0003-do-not-modify-PYTHONPATH-from-setup.py.patch22
-rw-r--r--package/debian10/patches/series2
-rw-r--r--package/debian10/py3dist-overrides1
-rw-r--r--package/debian10/python-silx-doc.doc-base (renamed from package/debian8/python-silx-doc.doc-base)0
-rwxr-xr-xpackage/debian10/rules85
-rw-r--r--package/debian10/source/format (renamed from package/debian8/source/format)0
-rw-r--r--package/debian10/source/options (renamed from package/debian8/source/options)0
-rw-r--r--package/debian10/tests/control31
-rw-r--r--package/debian10/watch7
-rw-r--r--package/debian8/changelog22
-rw-r--r--package/debian8/clean1
-rw-r--r--package/debian8/compat1
-rw-r--r--package/debian8/control134
-rwxr-xr-xpackage/debian8/rules52
-rw-r--r--package/debian8/watch5
-rw-r--r--package/debian9/control2
-rw-r--r--requirements.txt1
-rw-r--r--setup.py137
-rw-r--r--silx.egg-info/PKG-INFO3
-rw-r--r--silx.egg-info/SOURCES.txt74
-rw-r--r--silx/app/view/Viewer.py4
-rw-r--r--silx/gui/_glutils/OpenGLWidget.py11
-rwxr-xr-x[-rw-r--r--]silx/gui/colors.py27
-rw-r--r--silx/gui/data/DataViewer.py53
-rw-r--r--silx/gui/data/DataViews.py3
-rw-r--r--silx/gui/data/NumpyAxesSelector.py236
-rw-r--r--silx/gui/data/test/test_numpyaxesselector.py4
-rw-r--r--silx/gui/dialog/ColormapDialog.py125
-rwxr-xr-x[-rw-r--r--]silx/gui/hdf5/Hdf5Item.py152
-rwxr-xr-x[-rw-r--r--]silx/gui/hdf5/test/test_hdf5.py4
-rw-r--r--silx/gui/plot/ColorBar.py2
-rw-r--r--silx/gui/plot/CurvesROIWidget.py59
-rwxr-xr-x[-rw-r--r--]silx/gui/plot/LegendSelector.py207
-rw-r--r--silx/gui/plot/PlotInteraction.py208
-rwxr-xr-x[-rw-r--r--]silx/gui/plot/PlotWidget.py177
-rw-r--r--silx/gui/plot/PlotWindow.py49
-rw-r--r--silx/gui/plot/ScatterMaskToolsWidget.py7
-rw-r--r--silx/gui/plot/ScatterView.py45
-rw-r--r--silx/gui/plot/StackView.py32
-rw-r--r--silx/gui/plot/StatsWidget.py7
-rwxr-xr-x[-rw-r--r--]silx/gui/plot/actions/control.py2
-rw-r--r--silx/gui/plot/actions/fit.py22
-rw-r--r--silx/gui/plot/actions/histogram.py1
-rw-r--r--silx/gui/plot/actions/io.py42
-rwxr-xr-x[-rw-r--r--]silx/gui/plot/backends/BackendBase.py87
-rwxr-xr-x[-rw-r--r--]silx/gui/plot/backends/BackendMatplotlib.py496
-rwxr-xr-x[-rw-r--r--]silx/gui/plot/backends/BackendOpenGL.py990
-rw-r--r--silx/gui/plot/backends/glutils/GLPlotCurve.py165
-rw-r--r--silx/gui/plot/backends/glutils/GLPlotImage.py4
-rw-r--r--silx/gui/plot/backends/glutils/GLPlotTriangles.py9
-rw-r--r--silx/gui/plot/items/__init__.py4
-rw-r--r--silx/gui/plot/items/_pick.py70
-rw-r--r--silx/gui/plot/items/complex.py13
-rw-r--r--silx/gui/plot/items/core.py162
-rw-r--r--silx/gui/plot/items/curve.py40
-rw-r--r--silx/gui/plot/items/histogram.py44
-rw-r--r--silx/gui/plot/items/image.py26
-rwxr-xr-x[-rw-r--r--]silx/gui/plot/items/marker.py17
-rw-r--r--silx/gui/plot/items/roi.py200
-rw-r--r--silx/gui/plot/items/scatter.py429
-rw-r--r--silx/gui/plot/items/shape.py64
-rwxr-xr-x[-rw-r--r--]silx/gui/plot/test/testPlotWidget.py218
-rw-r--r--silx/gui/plot/test/testStats.py5
-rw-r--r--silx/gui/plot/tools/PositionInfo.py2
-rw-r--r--silx/gui/plot/tools/profile/_BaseProfileToolBar.py4
-rw-r--r--silx/gui/plot/tools/roi.py22
-rw-r--r--silx/gui/plot3d/_model/items.py96
-rw-r--r--silx/gui/plot3d/items/_pick.py39
-rw-r--r--silx/gui/plot3d/items/scatter.py3
-rw-r--r--silx/gui/plot3d/items/volume.py151
-rw-r--r--silx/gui/plot3d/scene/primitives.py9
-rw-r--r--silx/gui/plot3d/tools/PositionInfoWidget.py2
-rw-r--r--silx/gui/qt/_utils.py15
-rwxr-xr-x[-rw-r--r--]silx/gui/test/test_colors.py39
-rwxr-xr-x[-rw-r--r--]silx/gui/utils/__init__.py32
-rwxr-xr-xsilx/gui/utils/qtutils.py170
-rwxr-xr-x[-rw-r--r--]silx/gui/utils/test/__init__.py12
-rw-r--r--silx/gui/utils/test/test.py76
-rwxr-xr-xsilx/gui/utils/test/test_qtutils.py75
-rw-r--r--silx/gui/utils/test/test_testutils.py55
-rw-r--r--silx/gui/utils/testutils.py3
-rw-r--r--silx/gui/widgets/ColormapNameComboBox.py166
-rw-r--r--silx/gui/widgets/FrameBrowser.py2
-rwxr-xr-xsilx/gui/widgets/LegendIconWidget.py513
-rw-r--r--silx/gui/widgets/RangeSlider.py4
-rw-r--r--silx/image/bilinear.c24879
-rw-r--r--silx/image/marchingsquares/_mergeimpl.cpp33603
-rw-r--r--silx/image/shapes.c25400
-rw-r--r--silx/image/shapes.pyx2
-rw-r--r--silx/image/utils.py53
-rw-r--r--silx/io/dictdump.py16
-rwxr-xr-x[-rw-r--r--]silx/io/fabioh5.py22
-rw-r--r--silx/io/specfile.c38340
-rw-r--r--silx/io/specfile/src/sflabel.c4
-rw-r--r--silx/io/test/test_dictdump.py16
-rwxr-xr-x[-rw-r--r--]silx/io/test/test_fabioh5.py60
-rw-r--r--silx/io/url.py2
-rw-r--r--silx/math/chistogramnd.c34233
-rw-r--r--silx/math/chistogramnd_lut.c57012
-rw-r--r--silx/math/colormap.c49957
-rw-r--r--silx/math/combo.c43092
-rw-r--r--silx/math/fft/basefft.py3
-rw-r--r--silx/math/fft/clfft.py12
-rw-r--r--silx/math/fft/fftw.py72
-rw-r--r--silx/math/fft/test/test_fft.py3
-rw-r--r--silx/math/fit/filters.c24868
-rw-r--r--silx/math/fit/functions.c31679
-rw-r--r--silx/math/fit/peaks.c22459
-rw-r--r--silx/math/interpolate.pyx165
-rw-r--r--silx/math/marchingcubes.cpp26983
-rw-r--r--silx/math/medianfilter/medianfilter.cpp30527
-rw-r--r--silx/math/setup.py9
-rw-r--r--silx/math/test/__init__.py4
-rw-r--r--silx/math/test/test_interpolate.py136
-rw-r--r--silx/opencl/common.py38
-rw-r--r--silx/opencl/convolution.py124
-rw-r--r--silx/opencl/linalg.py8
-rw-r--r--silx/opencl/processing.py11
-rw-r--r--silx/opencl/projection.py11
-rw-r--r--silx/opencl/reconstruction.py33
-rw-r--r--silx/opencl/sinofilter.py21
-rw-r--r--silx/opencl/sparse.py82
-rw-r--r--silx/opencl/test/test_addition.py5
-rw-r--r--silx/opencl/test/test_convolution.py8
-rw-r--r--silx/opencl/test/test_kahan.py8
-rw-r--r--silx/opencl/test/test_linalg.py5
-rw-r--r--silx/opencl/test/test_sparse.py85
-rw-r--r--silx/opencl/utils.py93
-rw-r--r--silx/resources/gui/colormaps/cividis.npybin0 -> 3200 bytes
-rw-r--r--silx/resources/gui/icons/description-description.pngbin0 -> 756 bytes
-rw-r--r--silx/resources/gui/icons/description-description.svg13
-rw-r--r--silx/resources/gui/icons/description-error.pngbin0 -> 952 bytes
-rw-r--r--silx/resources/gui/icons/description-error.svg13
-rw-r--r--silx/resources/gui/icons/description-name.pngbin0 -> 822 bytes
-rw-r--r--silx/resources/gui/icons/description-name.svg13
-rw-r--r--silx/resources/gui/icons/description-program.pngbin0 -> 767 bytes
-rw-r--r--silx/resources/gui/icons/description-program.svg13
-rw-r--r--silx/resources/gui/icons/description-title.pngbin0 -> 707 bytes
-rw-r--r--silx/resources/gui/icons/description-title.svg13
-rw-r--r--silx/resources/gui/icons/description-value.pngbin0 -> 833 bytes
-rw-r--r--silx/resources/gui/icons/description-value.svg13
-rw-r--r--silx/resources/opencl/sparse.cl20
-rw-r--r--silx/sx/_plot.py50
-rw-r--r--silx/utils/files.py6
-rwxr-xr-x[-rw-r--r--]silx/utils/number.py6
-rwxr-xr-x[-rw-r--r--]silx/utils/test/__init__.py2
-rw-r--r--silx/utils/test/test_number.py4
-rwxr-xr-xsilx/utils/test/test_testutils.py96
-rwxr-xr-x[-rw-r--r--]silx/utils/testutils.py73
-rw-r--r--version.py6
179 files changed, 6478 insertions, 445775 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index ec0b01d..4fe299b 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,74 @@
Change Log
==========
+0.12.0rc0: 2019/12/16
+---------------------
+
+Python 2.7 is no longer officially supported (even if tests pass and most of the library should work).
+
+* silx view application:
+
+ * Added: keep the same axes selection when changing dataset except for the stack view (PR #2701, #2780)
+ * Added a Description column in the browsing tree to display NeXus title or name (PR #2804)
+ * Added support of URL as filename (PR #2750)
+
+* `silx.gui`:
+
+ * `silx.gui.plot`:
+
+ * Added scatter plot regular and irregular grid visualization mode (PR #2810, #2815, #2820, #2824, #2831)
+ * Added `baseline` argument to `PlotWidget` `addCurve` and `addHistogram` methods (PR #2715)
+ * Added right axis support to `PlotWidget` marker items (PR #2744)
+ * Added `BoundingRect` `PlotWidget` item (PR #2823)
+ * Added more markers to `PlotWidget` items using symbols (PR #2792)
+ * Improved and fixed `PlotWidget` and backends rendering and picking to guarantee rendering order of items (PR #2602, #2694, #2726, #2728, #2730, #2731, #2732, #2734, #2746, #2800, #2822, #2829)
+ * Improved `RegionOfInterest`: Added `sigItemChanged` signal, renamed `get|setLabel` to `get|setName` (PR #2684, #2729, #2794, #2803)
+ * Improved `StackView`: Allow to save dataset to HDF5 (PR #2813)
+
+ * `silx.gui.plot3d`:
+
+ * Added colormapped isosurface display to `ComplexField3D` (PR #2675)
+
+ * Miscellaneous:
+
+ * Added `cividis` colormap (PR #2763)
+ * Added `silx.gui.widgets.ColormapNameComboBox` widget (PR #2814)
+ * Added `silx.gui.widgets.LegendIconWidget` widget (PR #2783)
+ * Added `silx.gui.utils.blockSignals` context manager (PR #2697, #2702)
+ * Added `silx.gui.utils.qtutils.getQEventName` function (PR #2725)
+ * Added `silx.gui.colors.asQColor` function (PR #2753)
+ * Minor fixes (PR #2662, #2667, #2674, #2719, #2724, #2747, #2757, #2760, #2766, #2789, #2798, #2799, #2805, #2811, #2832, #2834)
+
+* `silx.opencl`:
+
+ * Added `silx.opencl.sparse.CSR` with support of different data types (PR #2671)
+ * Improved support of different platforms like PoCL (PR #2669, #2698, #2806)
+ * Moved non-OpenCL related utilities to `silx.opencl.utils` module (PR #2782)
+ * Fixed `silx.opencl.sinofilter.SinoFilter` to avoid importing scikit-cuda (PR #2721)
+ * Fixed kernel garbage collection (PR #2708)
+ * Fixed `silx.opencl.convolution.Convolution` (PR #2781)
+
+* `silx.math`/`silx.image`:
+
+ * Added trilinear interpolator: `silx.math.interpolate.interp3d` (PR #2678)
+ * Added `silx.image.utils.gaussian_kernel` function (PR #2782)
+ * Improved `silx.image.shapes.Polygon` argument check (PR #2761)
+ * Fixed and improved `silx.math.fft` with FFTW backend (PR #2751)
+
+* `silx.io`:
+
+ * Added `asarray=True` argument to `silx.io.dictdump.h5todict` function (PR #2692, #2767)
+ * Improved `silx.io.utils.DataUrl` (PR #2790)
+ * Increased max number of motors in `specfile` (PR #2817)
+ * Fixed data conversion when reading images with `fabio` (PR #2735)
+
+* Build, documentation and tests:
+
+ * Added `Cython` as a build dependency (PR #2795, #2807, #2808)
+ * Added Debian 10 packaging (PR #2670, #2672, #2666, #2686, #2706)
+ * Improvements: documentation (PR #2673, #2680, #2679, #2772, #2759, #2779, #2801, #2802, #2833), testing tools (PR #2704, #2796, #2818), `bootstrap.py` script (PR #2727, #2733)
+
+
0.11.0: 2019/07/03
------------------
diff --git a/PKG-INFO b/PKG-INFO
index 1f1f36b..9c1e298 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: silx
-Version: 0.11.0
+Version: 0.12.0b0
Summary: Software library for X-ray data analysis
Home-page: http://www.silx.org/
Author: data analysis unit
@@ -133,6 +133,7 @@ Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Scientific/Engineering :: Physics
Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/build-deb.sh b/build-deb.sh
index f87e565..7ca6d7e 100755
--- a/build-deb.sh
+++ b/build-deb.sh
@@ -3,7 +3,7 @@
# Project: Silx
# https://github.com/silx-kit/silx
#
-# Copyright (C) 2015-2017 European Synchrotron Radiation Facility, Grenoble, France
+# Copyright (C) 2015-2019 European Synchrotron Radiation Facility, Grenoble, France
#
# Principal author: Jérôme Kieffer (Jerome.Kieffer@ESRF.eu)
#
@@ -29,9 +29,9 @@
project=silx
source_project=silx
-version=$(python -c"import version; print(version.version)")
-strictversion=$(python -c"import version; print(version.strictversion)")
-debianversion=$(python -c"import version; print(version.debianversion)")
+version=$(python3 -c"import version; print(version.version)")
+strictversion=$(python3 -c"import version; print(version.strictversion)")
+debianversion=$(python3 -c"import version; print(version.debianversion)")
deb_name=$(echo "$source_project" | tr '[:upper:]' '[:lower:]')
@@ -44,15 +44,6 @@ then
#we are probably on a ubuntu platform
debian_version=$(cat /etc/debian_version | cut -d/ -f1)
case $debian_version in
- squeeze)
- debian_version=6
- ;;
- wheezy)
- debian_version=7
- ;;
- jessie)
- debian_version=8
- ;;
stretch)
debian_version=9
;;
@@ -88,13 +79,11 @@ optional arguments:
--help show this help text
--install install the packages generated at the end of
the process using 'sudo dpkg'
- --debian7 Simulate a debian7 system (fail-safe)
- --debian8 Simulate a debian 8 Jessie system
--debian9 Simulate a debian 9 Stretch system
+ --debian10 Simulate a debian 10 Buster system
"
install=0
-use_python3=0 #used only for stdeb
while :
do
@@ -107,24 +96,6 @@ do
install=1
shift
;;
- --python3)
- use_python3=1
- shift
- ;;
- --debian7)
- debian_version=7
- target_system=debian${debian_version}
- dist_directory=${project_directory}/dist/${target_system}
- build_directory=${project_directory}/build/${target_system}
- shift
- ;;
- --debian8)
- debian_version=8
- target_system=debian${debian_version}
- dist_directory=${project_directory}/dist/${target_system}
- build_directory=${project_directory}/build/${target_system}
- shift
- ;;
--debian9)
debian_version=9
target_system=debian${debian_version}
@@ -152,11 +123,11 @@ clean_up()
mkdir -p ${build_directory}
}
-build_deb_8_plus () {
+build_deb () {
echo "Build for debian 8 or newer using actual packaging"
tarname=${project}_${debianversion}.orig.tar.gz
clean_up
- python setup.py debian_src
+ python3 setup.py debian_src
cp -f dist/${tarname} ${build_directory}
if [ -f dist/${project}-testimages.tar.gz ]
then
@@ -208,9 +179,19 @@ build_deb_8_plus () {
#export PYBUILD_DISABLE_python3=test
#export DEB_BUILD_OPTIONS=nocheck
fi
-
+
+ case $debian_version in
+ 9)
+ debian_name=stretch
+ ;;
+ 10)
+ debian_name=buster
+ ;;
+ esac
+
dch -v ${debianversion}-1 "upstream development build of ${project} ${version}"
- dch --bpo "${project} snapshot ${version} built for ${target_system}"
+ dch -D ${debian_name}-backports -l~bpo${debian_version}+ "${project} snapshot ${version} built for ${target_system}"
+ #dch --bpo "${project} snapshot ${version} built for ${target_system}"
dpkg-buildpackage -r
rc=$?
@@ -230,47 +211,11 @@ build_deb_8_plus () {
fi
}
-build_deb_7_minus () {
- echo "Build for debian 7 or older using stdeb"
- tarname=${project}-${strictversion}.tar.gz
- clean_up
-
- python setup.py sdist
- cp -f dist/${tarname} ${build_directory}
- cd ${build_directory}
- tar -xzf ${tarname}
- cd ${project}-${strictversion}
-
- if [ $use_python3 = 1 ]
- then
- echo Using Python 2+3
- python3 setup.py --command-packages=stdeb.command sdist_dsc --with-python2=True --with-python3=True --no-python3-scripts=True build --no-cython bdist_deb
- rc=$?
- else
- echo Using Python 2
- # bdist_deb feed /usr/bin using setup.py entry-points
- python setup.py --command-packages=stdeb.command build --no-cython bdist_deb
- rc=$?
- fi
-
- # move packages to dist directory
- rm -rf ${dist_directory}
- mkdir -p ${dist_directory}
- mv -f deb_dist/*.deb ${dist_directory}
-
- # back to the root
- cd ../../..
-}
-if [ $debian_version -ge 8 ]
-then
- build_deb_8_plus
-else
- build_deb_7_minus
-fi
+build_deb
if [ $install -eq 1 ]; then
- sudo -v su -c "dpkg -i ${dist_directory}/*.deb"
+ sudo su -c "dpkg -i ${dist_directory}/*.deb"
fi
exit "$rc"
diff --git a/doc/source/ext/snapshotqt_directive.py b/doc/source/ext/snapshotqt_directive.py
index 81305e0..582b934 100644
--- a/doc/source/ext/snapshotqt_directive.py
+++ b/doc/source/ext/snapshotqt_directive.py
@@ -185,14 +185,9 @@ else:
_logger.info('generate screenshot for %s from %s, binding is %s'
'' % (filename, script_or_module, qt.BINDING))
- # Probe Qt binding
- if qt.BINDING == 'PyQt4':
- def grabWindow(winID):
- return qt.QPixmap.grabWindow(winID)
- elif qt.BINDING in ('PyQt5', 'PySide2'):
- def grabWindow(winID):
- screen = qt.QApplication.primaryScreen()
- return screen.grabWindow(winID)
+ def grabWindow(winID):
+ screen = qt.QApplication.primaryScreen()
+ return screen.grabWindow(winID)
global _count
_count = 15
@@ -234,9 +229,6 @@ else:
sys.path.append(
os.path.abspath(os.path.dirname(script_or_module)))
qt.QTimer.singleShot(_TIMEOUT, _grabActiveWindowAndClose)
- if sys.version_info < (3, ):
- execfile(script_or_module)
- else:
- with open(script_or_module) as f:
- code = compile(f.read(), script_or_module, 'exec')
- exec(code, globals(), locals())
+ with open(script_or_module) as f:
+ code = compile(f.read(), script_or_module, 'exec')
+ exec(code, globals(), locals())
diff --git a/doc/source/install.rst b/doc/source/install.rst
index 3862e4c..8e5220b 100644
--- a/doc/source/install.rst
+++ b/doc/source/install.rst
@@ -132,7 +132,7 @@ To install it, you need to download this file :
http://www.silx.org/pub/debian/silx.list
-and copy it into the /etc/apt/source.list.d folder.
+and copy it into the /etc/apt/sources.list.d folder.
Then run ``apt-get update`` and ``apt-get install python-silx``
.. code-block:: bash
diff --git a/doc/source/modules/opencl/convolution.rst b/doc/source/modules/opencl/convolution.rst
index 225d016..f33cc5b 100644
--- a/doc/source/modules/opencl/convolution.rst
+++ b/doc/source/modules/opencl/convolution.rst
@@ -5,6 +5,6 @@
-------------------------------
.. automodule:: silx.opencl.convolution
- :members: Convolution, gaussian_kernel
+ :members: Convolution
:show-inheritance:
:undoc-members:
diff --git a/doc/source/sample_code/img/compareImages.png b/doc/source/sample_code/img/compareImages.png
new file mode 100644
index 0000000..49618b5
--- /dev/null
+++ b/doc/source/sample_code/img/compareImages.png
Binary files differ
diff --git a/doc/source/sample_code/img/compositeline.png b/doc/source/sample_code/img/compositeline.png
new file mode 100644
index 0000000..8950286
--- /dev/null
+++ b/doc/source/sample_code/img/compositeline.png
Binary files differ
diff --git a/doc/source/sample_code/img/dropZones.png b/doc/source/sample_code/img/dropZones.png
new file mode 100644
index 0000000..3196c6b
--- /dev/null
+++ b/doc/source/sample_code/img/dropZones.png
Binary files differ
diff --git a/doc/source/sample_code/img/exampleBaseline.png b/doc/source/sample_code/img/exampleBaseline.png
new file mode 100644
index 0000000..fcd5778
--- /dev/null
+++ b/doc/source/sample_code/img/exampleBaseline.png
Binary files differ
diff --git a/doc/source/sample_code/img/findContours.png b/doc/source/sample_code/img/findContours.png
new file mode 100644
index 0000000..06dcf01
--- /dev/null
+++ b/doc/source/sample_code/img/findContours.png
Binary files differ
diff --git a/doc/source/sample_code/img/plotCurveLegendWidget.png b/doc/source/sample_code/img/plotCurveLegendWidget.png
new file mode 100644
index 0000000..d857950
--- /dev/null
+++ b/doc/source/sample_code/img/plotCurveLegendWidget.png
Binary files differ
diff --git a/doc/source/sample_code/img/plotStats.png b/doc/source/sample_code/img/plotStats.png
new file mode 100644
index 0000000..e4fc781
--- /dev/null
+++ b/doc/source/sample_code/img/plotStats.png
Binary files differ
diff --git a/doc/source/sample_code/img/scatterview.png b/doc/source/sample_code/img/scatterview.png
new file mode 100644
index 0000000..32f0235
--- /dev/null
+++ b/doc/source/sample_code/img/scatterview.png
Binary files differ
diff --git a/doc/source/sample_code/img/syncPlotLocation.png b/doc/source/sample_code/img/syncPlotLocation.png
new file mode 100644
index 0000000..0b86f3e
--- /dev/null
+++ b/doc/source/sample_code/img/syncPlotLocation.png
Binary files differ
diff --git a/doc/source/sample_code/index.rst b/doc/source/sample_code/index.rst
index 409391f..082579b 100644
--- a/doc/source/sample_code/index.rst
+++ b/doc/source/sample_code/index.rst
@@ -51,10 +51,6 @@ All sample codes can be downloaded as a zip file: |sample_code_archive|.
:width: 150px
- Qt Hdf5 widget examples
- .. note:: This module has a dependency on the `h5py <http://www.h5py.org/>`_
- library, which is not a mandatory dependency for `silx`. You might need
- to install it if you don't already have it.
-
:mod:`silx.gui.dialog`
......................
@@ -69,6 +65,10 @@ All sample codes can be downloaded as a zip file: |sample_code_archive|.
- .. image:: img/fileDialog.png
:width: 150px
- Example for the use of the ImageFileDialog.
+ * - :download:`colormapDialog.py <../../../examples/colormapDialog.py>`
+ - .. image:: img/colormapDialog.png
+ :width: 150px
+ - This script shows the features of a :mod:`~silx.gui.dialog.ColormapDialog`.
:mod:`silx.gui.widgets`
.......................
@@ -120,20 +120,38 @@ Widgets
``python examples/imageview.py <file to open>``
To get help:
``python examples/imageview.py -h``
-
- For developers with a git clone you can use it with the bootstrap
- To view an image file with the current installed silx library:
-
- ``./bootstrap.py python examples/imageview.py <file to open>``
* - :download:`stackView.py <../../../examples/stackView.py>`
- .. image:: img/stackView.png
:width: 150px
- This script is a simple example to illustrate how to use the
:mod:`~silx.gui.plot.StackView` widget.
- * - :download:`colormapDialog.py <../../../examples/colormapDialog.py>`
- - .. image:: img/colormapDialog.png
+ * - :download:`scatterview.py <../../../examples/scatterview.py>`
+ - .. image:: img/scatterview.png
:width: 150px
- - This script shows the features of a :mod:`~silx.gui.dialog.ColormapDialog`.
+ - Example to show the use of :class:`~silx.gui.plot.ScatterView.ScatterView` widget
+ * - :download:`compareImages.py <../../../examples/compareImages.py>`
+ - .. image:: img/compareImages.png
+ :width: 150px
+ - usage: compareImages.py [-h] [--debug] [--testdata] [--use-opengl-plot]
+ [files [files ...]]
+
+ Example demonstrating the use of the widget CompareImages
+
+ positional arguments:
+ files Image data to compare (HDF5 file with path, EDF files,
+ JPEG/PNG image files). Data from HDF5 files can be
+ accessed using dataset path and slicing as an URL:
+ silx:../my_file.h5?path=/entry/data&slice=10 EDF file
+ frames also can can be accessed using URL:
+ fabio:../my_file.edf?slice=10 Using URL in command like
+ usually have to be quoted: "URL".
+
+ optional arguments:
+ -h, --help show this help message and exit
+ --debug Set logging system in debug mode
+ --testdata Use synthetic images to test the application
+ --use-opengl-plot Use OpenGL for plots (instead of matplotlib)
+
:class:`silx.gui.plot.actions.PlotAction`
.........................................
@@ -183,7 +201,7 @@ Sample code that adds buttons to the toolbar of a silx plot widget.
Add features to :class:`~silx.gui.plot.PlotWidget`
..................................................
-Sample code that adds specific tools or functions to plot widgets.
+Sample code that adds specific tools or functions to :class:`~silx.gui.plot.PlotWidget`.
.. list-table::
:widths: 1 1 4
@@ -205,6 +223,70 @@ Sample code that adds specific tools or functions to plot widgets.
- QAction from :mod:`silx.gui.plot.actions`
- QToolButton from :mod:`silx.gui.plot.PlotToolButtons`
- :class:`silx.gui.plot.ColorBar.ColorBarWidget`
+ * - :download:`plotItemsSelector.py <../../../examples/plotItemsSelector.py>`
+ - .. image:: img/plotItemsSelector.png
+ :width: 150px
+ - This example illustrates how to use a :class:`ItemsSelectionDialog` widget
+ associated with a :class:`~silx.gui.plot.PlotWidget`
+ * - :download:`plotInteractiveImageROI.py <../../../examples/plotInteractiveImageROI.py>`
+ - .. image:: img/plotInteractiveImageROI.png
+ :width: 150px
+ - This script illustrates image ROI selection in a :class:`~silx.gui.plot.PlotWidget`
+
+ It uses :class:`~silx.gui.plot.tools.roi.RegionOfInterestManager` and
+ :class:`~silx.gui.plot.tools.roi.RegionOfInterestTableWidget` to handle the
+ interactive selection and to display the list of selected ROIs.
+ * - :download:`printPreview.py <../../../examples/printPreview.py>`
+ - .. image:: img/printPreview.png
+ :width: 150px
+ - This script illustrates how to add a print preview tool button to any plot
+ widget inheriting :class:`~silx.gui.plot.PlotWidget`.
+
+ Three plot widgets are instantiated. One of them uses a standalone
+ :class:`~silx.gui.plot.PrintPreviewToolButton.PrintPreviewToolButton`,
+ while the other two use a
+ :class:`~silx.gui.plot.PrintPreviewToolButton.SingletonPrintPreviewToolButton`
+ which allows them to send their content to the same print preview page.
+ * - :download:`scatterMask.py <../../../examples/scatterMask.py>`
+ - .. image:: img/scatterMask.png
+ :width: 150px
+ - This example demonstrates how to use ScatterMaskToolsWidget
+ and NamedScatterAlphaSlider with a PlotWidget.
+ * - :download:`plotCurveLegendWidget.py <../../../examples/plotCurveLegendWidget.py>`
+ - .. image:: img/plotCurveLegendWidget.png
+ :width: 150px
+ - This example illustrates the use of :class:`CurveLegendsWidget`.
+
+ :class:`CurveLegendsWidget` display curves style and legend currently visible
+ in a :class:`~silx.gui.plot.PlotWidget`
+ * - :download:`plotStats.py <../../../examples/plotStats.py>`
+ - .. image:: img/plotStats.png
+ :width: 150px
+ - This script is a simple example of how to add your own statistic to a
+ :class:`~silx.gui.plot.statsWidget.StatsWidget` from customs
+ :class:`~silx.gui.plot.stats.Stats` and display it.
+
+ On this example we will:
+
+ - show sum of values for each type
+ - compute curve integrals (only for 'curve').
+ - compute center of mass for all possible items
+
+ .. note:: for now the possible types manged by the Stats are ('curve', 'image',
+ 'scatter' and 'histogram')
+
+:class:`~silx.gui.plot.PlotWidget` features
+...........................................
+
+Sample code that illustrates some functionalities of :class:`~silx.gui.plot.PlotWidget`.
+
+.. list-table::
+ :widths: 1 1 4
+ :header-rows: 1
+
+ * - Source
+ - Screenshot
+ - Description
* - :download:`plotContextMenu.py <../../../examples/plotContextMenu.py>`
- .. image:: img/plotContextMenu.png
:width: 150px
@@ -221,11 +303,6 @@ Sample code that adds specific tools or functions to plot widgets.
inherit from :class:`~silx.gui.plot.PlotWidget`.
For more information on context menus, see Qt documentation.
- * - :download:`plotItemsSelector.py <../../../examples/plotItemsSelector.py>`
- - .. image:: img/plotItemsSelector.png
- :width: 150px
- - This example illustrates how to use a :class:`ItemsSelectionDialog` widget
- associated with a :class:`~silx.gui.plot.PlotWidget`
* - :download:`plotLimits.py <../../../examples/plotLimits.py>`
- .. image:: img/plotLimits.png
:width: 150px
@@ -257,36 +334,30 @@ Sample code that adds specific tools or functions to plot widgets.
In this example a thread calls submitToQtMainThread to update the curve
of a plot.
- * - :download:`plotInteractiveImageROI.py <../../../examples/plotInteractiveImageROI.py>`
- - .. image:: img/plotInteractiveImageROI.png
+ * - :download:`syncaxis.py <../../../examples/syncaxis.py>`
+ - .. image:: img/syncaxis.png
:width: 150px
- - This script illustrates image ROI selection in a :class:`~silx.gui.plot.PlotWidget`
-
- It uses :class:`~silx.gui.plot.tools.roi.RegionOfInterestManager` and
- :class:`~silx.gui.plot.tools.roi.RegionOfInterestTableWidget` to handle the
- interactive selection and to display the list of selected ROIs.
- * - :download:`printPreview.py <../../../examples/printPreview.py>`
- - .. image:: img/printPreview.png
+ - This script is an example to illustrate how to use axis synchronization
+ tool.
+ * - :download:`compositeline.py <../../../examples/compositeline.py>`
+ - .. image:: img/compositeline.png
:width: 150px
- - This script illustrates how to add a print preview tool button to any plot
- widget inheriting :class:`~silx.gui.plot.PlotWidget`.
-
- Three plot widgets are instantiated. One of them uses a standalone
- :class:`~silx.gui.plot.PrintPreviewToolButton.PrintPreviewToolButton`,
- while the other two use a
- :class:`~silx.gui.plot.PrintPreviewToolButton.SingletonPrintPreviewToolButton`
- which allows them to send their content to the same print preview page.
- * - :download:`scatterMask.py <../../../examples/scatterMask.py>`
- - .. image:: img/scatterMask.png
+ - Example to show the use of markers to draw head and tail of lines.
+ * - :download:`dropZones.py <../../../examples/dropZones.py>`
+ - .. image:: img/dropZones.png
:width: 150px
- - This example demonstrates how to use ScatterMaskToolsWidget
- and NamedScatterAlphaSlider with a PlotWidget.
- * - :download:`syncaxis.py <../../../examples/syncaxis.py>`
- - .. image:: img/syncaxis.png
+ - Example of drop zone supporting application/x-silx-uri
+ * - :download:`exampleBaseline.py <../../../examples/exampleBaseline.py>`
+ - .. image:: img/exampleBaseline.png
+ :width: 150px
+ - This example illustrates some usage possible with the baseline parameter
+ * - :download:`syncPlotLocation.py <../../../examples/syncPlotLocation.py>`
+ - .. image:: img/syncPlotLocation.png
:width: 150px
- This script is an example to illustrate how to use axis synchronization
tool.
+
.. _plot3d-sample-code:
:mod:`silx.gui.plot3d` sample code
@@ -299,25 +370,6 @@ Sample code that adds specific tools or functions to plot widgets.
* - Source
- Screenshot
- Description
- * - :download:`plot3dContextMenu.py <../../../examples/plot3dContextMenu.py>`
- - .. image:: img/plot3dContextMenu.png
- :width: 150px
- - This script adds a context menu to a :class:`silx.gui.plot3d.ScalarFieldView`.
-
- This is done by adding a custom context menu to the :class:`Plot3DWidget`:
-
- - set the context menu policy to Qt.CustomContextMenu.
- - connect to the customContextMenuRequested signal.
-
- For more information on context menus, see Qt documentation.
- * - :download:`viewer3DVolume.py <../../../examples/viewer3DVolume.py>`
- - .. image:: img/viewer3DVolume.png
- :width: 150px
- - This script illustrates the use of :class:`silx.gui.plot3d.ScalarFieldView`.
-
- It loads a 3D scalar data set from a file and displays iso-surfaces and
- an interactive cutting plane.
- It can also be started without providing a file.
* - :download:`plot3dSceneWindow.py <../../../examples/plot3dSceneWindow.py>`
- .. image:: img/plot3dSceneWindow.png
:width: 150px
@@ -347,6 +399,25 @@ Sample code that adds specific tools or functions to plot widgets.
:func:`~silx.gui.utils.concurrent.submitToQtMainThread`.
In this example a thread calls submitToQtMainThread to append data to a 3D scatter.
+ * - :download:`plot3dContextMenu.py <../../../examples/plot3dContextMenu.py>`
+ - .. image:: img/plot3dContextMenu.png
+ :width: 150px
+ - This script adds a context menu to a :class:`silx.gui.plot3d.ScalarFieldView`.
+
+ This is done by adding a custom context menu to the :class:`Plot3DWidget`:
+
+ - set the context menu policy to Qt.CustomContextMenu.
+ - connect to the customContextMenuRequested signal.
+
+ For more information on context menus, see Qt documentation.
+ * - :download:`viewer3DVolume.py <../../../examples/viewer3DVolume.py>`
+ - .. image:: img/viewer3DVolume.png
+ :width: 150px
+ - This script illustrates the use of :class:`silx.gui.plot3d.ScalarFieldView`.
+
+ It loads a 3D scalar data set from a file and displays iso-surfaces and
+ an interactive cutting plane.
+ It can also be started without providing a file.
:mod:`silx.io` sample code
@@ -363,3 +434,22 @@ Sample code that adds specific tools or functions to plot widgets.
-
- This script is an example of how to use the :mod:`silx.io.convert` module.
See the following tutorial for more information: :doc:`../Tutorials/convert`
+
+
+:mod:`silx.image` sample code
++++++++++++++++++++++++++++++
+
+.. list-table::
+ :widths: 1 1 4
+ :header-rows: 1
+
+ * - Source
+ - Screenshot
+ - Description
+ * - :download:`findContours.py <../../../examples/findContours.py>`
+ - .. image:: img/findContours.png
+ :width: 150px
+ - Find contours examples
+
+ .. note:: This module has an optional dependency with sci-kit image library.
+ You might need to install it if you don't already have it.
diff --git a/examples/compositeline.py b/examples/compositeline.py
new file mode 100644
index 0000000..892ecf3
--- /dev/null
+++ b/examples/compositeline.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2016-2019 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""
+Example to show the use of markers to draw head and tail of lines.
+"""
+from __future__ import division
+
+__license__ = "MIT"
+
+import logging
+from silx.gui.plot import Plot1D
+from silx.gui import qt
+import numpy
+
+
+logging.basicConfig()
+logger = logging.getLogger(__name__)
+
+
+def main(argv=None):
+ """Display few lines with markers.
+ """
+ global app # QApplication must be global to avoid seg fault on quit
+ app = qt.QApplication([])
+ sys.excepthook = qt.exceptionHandler
+
+ mainWindow = Plot1D(backend="gl")
+ mainWindow.setAttribute(qt.Qt.WA_DeleteOnClose)
+ plot = mainWindow
+ plot.setDataMargins(0.1, 0.1, 0.1, 0.1)
+
+ plot.addCurve(x=[-10,0,0,-10,-10], y=[90,90,10,10,90], legend="box1", color="gray")
+ plot.addCurve(x=[110,100,100,110,110], y=[90,90,10,10,90], legend="box2", color="gray")
+ plot.addCurve(y=[-10,0,0,-10,-10], x=[90,90,10,10,90], legend="box3", color="gray")
+ plot.addCurve(y=[110,100,100,110,110], x=[90,90,10,10,90], legend="box4", color="gray")
+
+ def addLine(source, destination, symbolSource, symbolDestination, legend, color):
+ line = numpy.array([source, destination]).T
+ plot.addCurve(x=line[0,:], y=line[1,:], color=color, legend=legend)
+ plot.addMarker(x=source[0], y=source[1], symbol=symbolSource, color=color)
+ plot.addMarker(x=destination[0], y=destination[1], symbol=symbolDestination, color=color)
+
+ addLine([0, 50], [100, 50], "caretleft", "caretright", "l1", "red")
+ addLine([0, 30], [100, 30], "tickup", "tickdown", "l2", "blue")
+ addLine([0, 70], [100, 70], "|", "|", "l3", "black")
+
+ addLine([50, 0], [50, 100], "caretdown", "caretup", "l4", "red")
+ addLine([30, 0], [30, 100], "tickleft", "tickright", "l5", "blue")
+ addLine([70, 0], [70, 100], "_", "_", "l6", "black")
+
+ mainWindow.setVisible(True)
+ return app.exec_()
+
+
+if __name__ == "__main__":
+ import sys
+ sys.exit(main(argv=sys.argv[1:]))
diff --git a/examples/exampleBaseline.py b/examples/exampleBaseline.py
new file mode 100644
index 0000000..edd0fc3
--- /dev/null
+++ b/examples/exampleBaseline.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2017-2018 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""This example illustrates some usage possible with the baseline parameter
+"""
+
+__authors__ = ["H. Payno"]
+__license__ = "MIT"
+__date__ = "12/09/2019"
+
+
+from silx.gui import qt
+from silx.gui.plot import Plot1D
+import numpy
+import sys
+import argparse
+
+
+def stacked_histogran(plot, edges, histograms, colors, legend):
+ # check that we have the same number of histogram, color and baseline
+ current_baseline = numpy.zeros_like(edges)
+
+ for histogram, color, layer_index in zip(histograms, colors, range(len(colors))):
+ stacked_histo = histogram + current_baseline
+ plot.addHistogram(histogram=stacked_histo,
+ edges=edges,
+ legend='_'.join((legend, str(layer_index))),
+ color=color,
+ baseline=current_baseline,
+ z=len(histograms)-layer_index,
+ fill=True)
+ current_baseline = stacked_histo
+
+
+def get_plot_std(backend):
+ x = numpy.arange(0, 10, step=0.1)
+ my_sin = numpy.sin(x)
+ y = numpy.arange(-4, 6, step=0.1) + my_sin
+ mean = numpy.arange(-5, 5, step=0.1) + my_sin
+ baseline = numpy.arange(-6, 4, step=0.1) + my_sin
+ edges = x[y >= 3.0]
+ histo = mean[y >= 3.0] - 1.8
+
+ plot = Plot1D(backend=backend)
+ plot.addCurve(x=x, y=y, baseline=baseline, color='grey',
+ legend='std-curve', fill=True)
+ plot.addCurve(x=x, y=mean, color='red', legend='mean')
+ plot.addHistogram(histogram=histo, edges=edges, color='red',
+ legend='mean2', fill=True)
+ return plot
+
+
+def get_plot_stacked_histogram(backend):
+ plot = Plot1D(backend=backend)
+ # first histogram
+ edges = numpy.arange(-6, 6, step=0.5)
+ histo_1 = numpy.random.random(len(edges))
+ histo_2 = numpy.random.random(len(edges))
+ histo_3 = numpy.random.random(len(edges))
+ histo_4 = numpy.random.random(len(edges))
+ stacked_histogran(plot=plot,
+ edges=edges,
+ histograms=(histo_1, histo_2, histo_3, histo_4),
+ colors=('blue', 'green', 'red', 'yellow'),
+ legend='first_stacked_histo')
+
+ # second histogram
+ edges = numpy.arange(10, 25, step=1.0)
+ histo_1 = -numpy.random.random(len(edges))
+ histo_2 = -numpy.random.random(len(edges))
+ stacked_histogran(plot=plot, histograms=(histo_1, histo_2),
+ edges=edges,
+ colors=('gray', 'black'),
+ legend='second_stacked_histo')
+
+ # last histogram
+ edges = [30, 40]
+ histograms = [
+ [0.2, 0.3],
+ [0.0, 1.0],
+ [0.1, 0.4],
+ [0.2, 0.0],
+ [0.6, 0.4],
+ ]
+ stacked_histogran(plot=plot,
+ histograms=histograms,
+ edges=edges,
+ colors=('blue', 'green', 'red', 'yellow', 'cyan'),
+ legend='third_stacked_histo')
+
+ return plot
+
+
+def get_plot_mean_baseline(backend):
+ plot = Plot1D(backend=backend)
+ x = numpy.arange(0, 10, step=0.1)
+ y = numpy.sin(x)
+ plot.addCurve(x=x, y=y, baseline=0, fill=True)
+ plot.setYAxisLogarithmic(True)
+ return plot
+
+
+def get_plot_log(backend):
+ plot = Plot1D(backend=backend)
+ x = numpy.arange(0, 10, step=0.01)
+ y = numpy.exp2(x)
+ baseline = numpy.exp(x)
+ plot.addCurve(x=x, y=y, baseline=baseline, fill=True)
+ plot.setYAxisLogarithmic(True)
+ return plot
+
+
+def main(argv):
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument(
+ '--backend',
+ dest="backend",
+ action="store",
+ default=None,
+ help='Set plot backend. Should be "matplotlib" (default) or "opengl"')
+
+ options = parser.parse_args(argv[1:])
+ assert options.backend in (None, 'matplotlib', 'opengl')
+ qapp = qt.QApplication([])
+
+ plot_std = get_plot_std(backend=options.backend)
+ plot_std.show()
+
+ plot_mean = get_plot_mean_baseline(backend=options.backend)
+ plot_mean.show()
+
+ plot_stacked_histo = get_plot_stacked_histogram(backend=options.backend)
+ plot_stacked_histo.show()
+
+ plot_log = get_plot_log(backend=options.backend)
+ plot_log.show()
+
+ qapp.exec_()
+
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/examples/findContours.py b/examples/findContours.py
index a5bb663..a7b5ac4 100644
--- a/examples/findContours.py
+++ b/examples/findContours.py
@@ -2,7 +2,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2016-2018 European Synchrotron Radiation Facility
+# Copyright (c) 2016-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -25,7 +25,7 @@
# ###########################################################################*/
"""Find contours examples
-.. note:: This module has an optional dependancy with sci-kit image library.
+.. note:: This module has an optional dependency with sci-kit image library.
You might need to install it if you don't already have it.
"""
diff --git a/examples/hdf5widget.py b/examples/hdf5widget.py
index c344bec..217eb7f 100755
--- a/examples/hdf5widget.py
+++ b/examples/hdf5widget.py
@@ -2,7 +2,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2016-2018 European Synchrotron Radiation Facility
+# Copyright (c) 2016-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -23,12 +23,7 @@
# THE SOFTWARE.
#
# ###########################################################################*/
-"""Qt Hdf5 widget examples
-
-.. note:: This module has a dependency on the `h5py <http://www.h5py.org/>`_
- library, which is not a mandatory dependency for `silx`. You might need
- to install it if you don't already have it.
-"""
+"""Qt Hdf5 widget examples"""
import logging
import sys
diff --git a/examples/imageview.py b/examples/imageview.py
index 71bde70..5c7eddb 100755
--- a/examples/imageview.py
+++ b/examples/imageview.py
@@ -2,7 +2,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2016-2018 European Synchrotron Radiation Facility
+# Copyright (c) 2016-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -32,11 +32,6 @@ To view an image file with the current installed silx library:
``python examples/imageview.py <file to open>``
To get help:
``python examples/imageview.py -h``
-
-For developers with a git clone you can use it with the bootstrap
-To view an image file with the current installed silx library:
-
-``./bootstrap.py python examples/imageview.py <file to open>``
"""
from __future__ import division
diff --git a/examples/plotInteractiveImageROI.py b/examples/plotInteractiveImageROI.py
index e06db89..8a4019f 100644
--- a/examples/plotInteractiveImageROI.py
+++ b/examples/plotInteractiveImageROI.py
@@ -2,7 +2,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2018 European Synchrotron Radiation Facility
+# Copyright (c) 2018-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -71,13 +71,12 @@ roiManager.setColor('pink') # Set the color of ROI
# Set the name of each created region of interest
def updateAddedRegionOfInterest(roi):
"""Called for each added region of interest: set the name"""
- if roi.getLabel() == '':
- roi.setLabel('ROI %d' % len(roiManager.getRois()))
+ if roi.getName() == '':
+ roi.setName('ROI %d' % len(roiManager.getRois()))
if isinstance(roi, LineMixIn):
roi.setLineWidth(2)
roi.setLineStyle('--')
if isinstance(roi, SymbolMixIn):
- roi.setSymbol('o')
roi.setSymbolSize(5)
@@ -86,7 +85,7 @@ roiManager.sigRoiAdded.connect(updateAddedRegionOfInterest)
# Add a rectangular region of interest
roi = RectangleROI()
roi.setGeometry(origin=(50, 50), size=(200, 200))
-roi.setLabel('Initial ROI')
+roi.setName('Initial ROI')
roiManager.addRoi(roi)
# Create the table widget displaying
diff --git a/examples/scatterview.py b/examples/scatterview.py
new file mode 100755
index 0000000..cab32c0
--- /dev/null
+++ b/examples/scatterview.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2016-2019 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""
+Example to show the use of :class:`~silx.gui.plot.ScatterView.ScatterView` widget.
+"""
+from __future__ import division
+
+__license__ = "MIT"
+
+import logging
+from silx.gui.plot.ScatterView import ScatterView
+from silx.gui import qt
+import numpy
+import scipy.signal
+
+
+logging.basicConfig()
+logger = logging.getLogger(__name__)
+
+
+def createData():
+ nbPoints = 200
+ nbX = int(numpy.sqrt(nbPoints))
+ nbY = nbPoints // nbX + 1
+
+ # Motor position
+ yy = numpy.atleast_2d(numpy.ones(nbY)).T
+ xx = numpy.atleast_2d(numpy.ones(nbX))
+
+ positionX = numpy.linspace(10, 50, nbX) * yy
+ positionX = positionX.reshape(nbX * nbY)
+ positionX = positionX + numpy.random.rand(len(positionX)) - 0.5
+
+ positionY = numpy.atleast_2d(numpy.linspace(20, 60, nbY)).T * xx
+ positionY = positionY.reshape(nbX * nbY)
+ positionY = positionY + numpy.random.rand(len(positionY)) - 0.5
+
+ # Diodes position
+ lut = scipy.signal.gaussian(max(nbX, nbY), std=8) * 10
+ yy, xx = numpy.ogrid[:nbY, :nbX]
+ signal = lut[yy] * lut[xx]
+ diode1 = numpy.random.poisson(signal * 10)
+ diode1 = diode1.reshape(nbX * nbY)
+ return positionX, positionY, diode1
+
+
+def main(argv=None):
+ """Display an image from a file in an :class:`ImageView` widget.
+
+ :param argv: list of command line arguments or None (the default)
+ to use sys.argv.
+ :type argv: list of str
+ :return: Exit status code
+ :rtype: int
+ :raises IOError: if no image can be loaded from the file
+ """
+ import argparse
+ import os.path
+
+ global app # QApplication must be global to avoid seg fault on quit
+ app = qt.QApplication([])
+ sys.excepthook = qt.exceptionHandler
+
+ mainWindow = ScatterView()
+ mainWindow.setAttribute(qt.Qt.WA_DeleteOnClose)
+ xx, yy, value = createData()
+ mainWindow.setData(x=xx, y=yy, value=value)
+ mainWindow.show()
+ mainWindow.setFocus(qt.Qt.OtherFocusReason)
+
+ return app.exec_()
+
+
+if __name__ == "__main__":
+ import sys
+ sys.exit(main(argv=sys.argv[1:]))
diff --git a/examples/simplewidget.py b/examples/simplewidget.py
index e952dc6..88977b7 100755
--- a/examples/simplewidget.py
+++ b/examples/simplewidget.py
@@ -44,6 +44,7 @@ from silx.gui.colors import Colormap
from silx.gui.widgets.WaitingPushButton import WaitingPushButton
from silx.gui.widgets.ThreadPoolPushButton import ThreadPoolPushButton
from silx.gui.widgets.RangeSlider import RangeSlider
+from silx.gui.widgets.LegendIconWidget import LegendIconWidget
class SimpleWidgetExample(qt.QMainWindow):
@@ -69,6 +70,10 @@ class SimpleWidgetExample(qt.QMainWindow):
layout.addWidget(self.createRangeSlider())
layout.addWidget(self.createRangeSliderWithBackground())
+ panel = self.createLegendIconPanel(self)
+ layout.addWidget(qt.QLabel("LegendIconWidget"))
+ layout.addWidget(panel)
+
self.setCentralWidget(main_panel)
def createWaitingPushButton(self):
@@ -122,6 +127,65 @@ class SimpleWidgetExample(qt.QMainWindow):
widget.setGroovePixmapFromProfile(background, colormap)
return widget
+ def createLegendIconPanel(self, parent):
+ panel = qt.QWidget(parent)
+ layout = qt.QVBoxLayout(panel)
+
+ # Empty
+ legend = LegendIconWidget(panel)
+ layout.addWidget(legend)
+
+ # Line
+ legend = LegendIconWidget(panel)
+ legend.setLineStyle("-")
+ legend.setLineColor("blue")
+ legend.setLineWidth(2)
+ layout.addWidget(legend)
+
+ # Symbol
+ legend = LegendIconWidget(panel)
+ legend.setSymbol("o")
+ legend.setSymbolColor("red")
+ layout.addWidget(legend)
+
+ # Line and symbol
+ legend = LegendIconWidget(panel)
+ legend.setLineStyle(":")
+ legend.setLineColor("green")
+ legend.setLineWidth(2)
+ legend.setSymbol("x")
+ legend.setSymbolColor("violet")
+ layout.addWidget(legend)
+
+ # Colormap
+ legend = LegendIconWidget(panel)
+ legend.setColormap("viridis")
+ layout.addWidget(legend)
+
+ # Symbol and colormap
+ legend = LegendIconWidget(panel)
+ legend.setSymbol("o")
+ legend.setSymbolColormap("viridis")
+ layout.addWidget(legend)
+
+ # Symbol (without surface) and colormap
+ legend = LegendIconWidget(panel)
+ legend.setSymbol("+")
+ legend.setSymbolColormap("plasma")
+ layout.addWidget(legend)
+
+ # Colormap + Line + Symbol
+ legend = LegendIconWidget(panel)
+ legend.setColormap("gray")
+ legend.setLineStyle("-")
+ legend.setLineColor("white")
+ legend.setLineWidth(3)
+ legend.setSymbol(".")
+ legend.setSymbolColormap("red")
+ layout.addWidget(legend)
+
+ return panel
+
def main():
"""
diff --git a/package/debian10/changelog b/package/debian10/changelog
new file mode 100644
index 0000000..3b5427f
--- /dev/null
+++ b/package/debian10/changelog
@@ -0,0 +1,141 @@
+silx (0.11.0+dfsg-1) unstable; urgency=medium
+
+ [ Alexandre Marie ]
+ * Added test on openCL's use
+ * New upstream version 0.11.0+dfsg
+ * d/patches
+ - 0004-fix-missing-import.patches (Removed)
+ - 0005-fix-problem-with-sift-import (Removed)
+
+ -- Picca Frédéric-Emmanuel <picca@debian.org> Tue, 09 Jul 2019 15:26:55 +0200
+
+silx (0.10.1+dfsg-1~exp2) experimental; urgency=medium
+
+ * d/patchs
+ + 0005-fix-problem-with-sift-import.patch (Added)
+
+ -- Picca Frédéric-Emmanuel <picca@synchrotron-soleil.fr> Tue, 28 May 2019 11:17:46 +0200
+
+silx (0.10.1+dfsg-1~exp1) experimental; urgency=medium
+
+ * New upstream version 0.10.1+dfsg
+ * d/patches
+ + 0004-fix-missing-import.patch (Added)
+
+ -- Picca Frédéric-Emmanuel <picca@debian.org> Tue, 28 May 2019 08:20:44 +0200
+
+silx (0.9.0+dfsg-3) unstable; urgency=medium
+
+ * d/rules: Do not run Qt test for now.
+ * d/t/control.autodep8: Fixed to run test for real.
+
+ -- Picca Frédéric-Emmanuel <picca@debian.org> Thu, 21 Feb 2019 11:22:03 +0100
+
+silx (0.9.0+dfsg-2) unstable; urgency=medium
+
+ * d/patches:
+ + 0004-fix-FTBFS-with-numpy-0.16.patch (Added)
+
+ -- Picca Frédéric-Emmanuel <picca@debian.org> Thu, 20 Dec 2018 16:21:18 +0100
+
+silx (0.9.0+dfsg-1) unstable; urgency=medium
+
+ [ Picca Frédéric-Emmanuel ]
+ * Fixed autopkgtests and use control.autodep8
+ * Used salsa-ci for continuous integration.
+ * Run autopkgtests via xvfb-run
+ * d/control: Removed Build-Depends: python-lxml[-dbg], python-enum34.
+
+ [ Alexandre Marie ]
+ * New upstream version 0.9.0+dfsg
+ * d/watch: uversionmangling to deal with rc|alpha|beta versions.
+
+ -- Alexandre Marie <alexandre.marie@synchrotron-soleil.fr> Mon, 17 Dec 2018 13:25:52 +0100
+
+silx (0.8.0+dfsg-1) unstable; urgency=medium
+
+ * New upstream version 0.8.0+dfsg
+ * Added the pub 4096R/26F8E116 key to keyring
+ 2016-04-11 Thomas Vincent <thomas.vincent@esrf.fr>
+ * d/control
+ - Build-Depends
+ + Added pandoc
+ - Removed obsolete X-Python[3]-Version
+ * d/rules
+ - Installed the QtDesgigner files only for Qt5.
+ - Override dh_python3 to deal with qtdesigner files.
+ - Run sphinx with xvfb in order to have the right silx.sx documentation.
+ - Avoid QT warnings by setting XDG_RUNTIME_DIR
+
+ -- Picca Frédéric-Emmanuel <picca@debian.org> Tue, 31 Jul 2018 16:24:57 +0200
+
+silx (0.7.0+dfsg-2) unstable; urgency=medium
+
+ * d/rules
+ - use py3versions to get the python3 default interpreter version.
+ This makes the package backportable.
+
+ -- Picca Frédéric-Emmanuel <picca@debian.org> Tue, 13 Mar 2018 20:04:20 +0100
+
+silx (0.7.0+dfsg-1) unstable; urgency=medium
+
+ * New upstream version 0.7.0+dfsg
+ * Bumped Strandards-Versions to 4.1.3 (nothing to do)
+ * d/control
+ - Build-Depends
+ + Added python[3]-nbsphinx, python-concurrent.futures
+ * d/copyright
+ remove the third_party _local files.
+ * d/patches
+ + 0003-do-not-modify-PYTHONPATH-from-setup.py.patch (added)
+ - 0005-slocale.h-is-removed-in-GLIBC-2.26.patch (obsolete)
+ - 0006-prefer-pyqt5-over-pyside.patch (obsolete)
+ * d/rules
+ - removed the jessie backports specific code
+ - compile extensions only once per interpreter.
+ - unactive for now the build time tests.
+ - build the doc only with python3.
+ * d/watch
+ - check the pgp signature
+
+ -- Picca Frédéric-Emmanuel <picca@debian.org> Tue, 13 Mar 2018 07:32:00 +0100
+
+silx (0.6.1+dfsg-2) unstable; urgency=medium
+
+ * d/control
+ - Bump Standrad-Version 4.1.1 (nothing to do)
+ * fixed glibc 2.26 FTBFS with upstream patch glib2.26 (Closes: #882881)
+ * d/patches
+ + 0005-slocale.h-is-removed-in-GLIBC-2.26.patch (Added)
+
+ -- Picca Frédéric-Emmanuel <picca@debian.org> Sun, 21 Jan 2018 09:32:38 +0100
+
+silx (0.6.1+dfsg-1) unstable; urgency=medium
+
+ * New upstream version 0.6.1+dfsg
+ * update watch file
+
+ -- Picca Frédéric-Emmanuel <picca@debian.org> Sat, 25 Nov 2017 17:02:19 +0100
+
+silx (0.6.0+dfsg-1) unstable; urgency=medium
+
+ * New upstream version 0.6.0+dfsg
+ * d/patches
+ - 0001-fix-the-build_man-target.patch (deleted)
+ - 0004-test-unactive-ressource-for-now.patch (deleted)
+ - 0005-fix-the-sift-removal.patch (deleted)
+
+ -- Picca Frédéric-Emmanuel <picca@debian.org> Sat, 07 Oct 2017 08:08:56 +0200
+
+silx (0.5.0+dfsg-2) unstable; urgency=medium
+
+ * d/control
+ - Added all the -dbg dependencies for the -dbg packages.
+
+ -- Picca Frédéric-Emmanuel <picca@debian.org> Fri, 01 Sep 2017 15:10:44 +0200
+
+silx (0.5.0+dfsg-1) unstable; urgency=medium
+
+ * Initial release (Closes: #871637)
+
+ -- Picca Frédéric-Emmanuel <picca@debian.org> Wed, 02 Aug 2017 11:00:20 +0100
diff --git a/package/debian10/compat b/package/debian10/compat
new file mode 100644
index 0000000..f599e28
--- /dev/null
+++ b/package/debian10/compat
@@ -0,0 +1 @@
+10
diff --git a/package/debian10/control b/package/debian10/control
new file mode 100644
index 0000000..f16ccea
--- /dev/null
+++ b/package/debian10/control
@@ -0,0 +1,172 @@
+Source: silx
+Maintainer: Debian Science Maintainers <debian-science-maintainers@lists.alioth.debian.org>
+Uploaders: Jerome Kieffer <jerome.kieffer@esrf.fr>,
+ Picca Frédéric-Emmanuel <picca@debian.org>,
+ Alexandre Marie <alexandre.marie@synchrotron-soleil.fr>
+Section: science
+Priority: optional
+Build-Depends: cython (>= 0.23.2),
+ cython3 (>= 0.23.2),
+ debhelper (>= 10),
+ dh-python,
+ help2man,
+ ipython,
+ ipython-qtconsole,
+ ipython3,
+ ipython3-qtconsole,
+ pandoc <!nodoc>,
+ python-all-dev,
+ python-concurrent.futures,
+ python-fabio,
+ python-h5py,
+ python-mako,
+ python-matplotlib,
+ python-nbsphinx <!nodoc>,
+ python-numpy,
+ python-opengl,
+ python-pil,
+ python-pyopencl,
+ python-pyqt5,
+ python-pyqt5.qtopengl,
+ python-pyqt5.qtsvg,
+ python-scipy,
+ python-setuptools,
+ python-sphinx,
+ python-sphinxcontrib.programoutput,
+ python3-all-dev,
+ python3-dateutil,
+ python3-qtconsole,
+ python3-six,
+ python3-fabio,
+ python3-h5py,
+ python3-mako,
+ python3-matplotlib,
+ python3-nbsphinx <!nodoc>,
+ python3-numpy,
+ python3-opengl,
+ python3-pil,
+ python3-pyopencl,
+ python3-pyqt5,
+ python3-pyqt5.qtopengl,
+ python3-pyqt5.qtsvg,
+ python3-scipy,
+ python3-setuptools,
+ python3-sphinx,
+ python3-sphinxcontrib.programoutput,
+ xauth,
+ xvfb,
+ openstack-pkg-tools,
+ locales,
+ devscripts
+Standards-Version: 4.1.3
+Vcs-Browser: https://salsa.debian.org/science-team/silx
+Vcs-Git: https://salsa.debian.org/science-team/silx.git
+Homepage: https://github.com/silx-kit/silx
+
+Package: silx
+Architecture: all
+Depends: python3-silx (>= ${source:Version}),
+ ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends}
+Description: Toolbox for X-Ray data analysis - Executables
+ The silx project aims at providing a collection of Python packages to
+ support the development of data assessment, reduction and analysis
+ applications at synchrotron radiation facilities. It aims at
+ providing reading/writing different file formats, data reduction
+ routines and a set of Qt widgets to browse and visualize data.
+ .
+ The current version provides :
+ .
+ * reading HDF5 file format (with support of SPEC file format)
+ * histogramming
+ * fitting
+ * 1D and 2D visualization using multiple backends (matplotlib or OpenGL)
+ * image plot widget with a set of associated tools (See changelog file).
+ * Unified browser for HDF5, SPEC and image file formats supporting inspection
+ and visualization of n-dimensional datasets.
+ * Unified viewer (silx view filename) for HDF5, SPEC and image file formats
+ * OpenGL-based widget to display 3D scalar field with
+ isosurface and cutting plane.
+ .
+ This uses the Python 3 version of the package.
+
+Package: python-silx
+Architecture: any
+Section: python
+Depends: ${misc:Depends}, ${python:Depends}, ${shlibs:Depends}
+Description: Toolbox for X-Ray data analysis - Python2 library
+ The silx project aims at providing a collection of Python packages to
+ support the development of data assessment, reduction and analysis
+ applications at synchrotron radiation facilities. It aims at
+ providing reading/writing different file formats, data reduction
+ routines and a set of Qt widgets to browse and visualize data.
+ .
+ The current version provides :
+ .
+ * reading HDF5 file format (with support of SPEC file format)
+ * histogramming
+ * fitting
+ * 1D and 2D visualization using multiple backends (matplotlib or OpenGL)
+ * image plot widget with a set of associated tools (See changelog file).
+ * Unified browser for HDF5, SPEC and image file formats supporting inspection
+ and visualization of n-dimensional datasets.
+ * Unified viewer (silx view filename) for HDF5, SPEC and image file formats
+ * OpenGL-based widget to display 3D scalar field with
+ isosurface and cutting plane.
+ .
+ This is the Python 2 version of the package.
+
+
+Package: python3-silx
+Architecture: any
+Section: python
+Depends: ${misc:Depends}, ${python3:Depends}, ${shlibs:Depends}
+Description: Toolbox for X-Ray data analysis - Python3
+ The silx project aims at providing a collection of Python packages to
+ support the development of data assessment, reduction and analysis
+ applications at synchrotron radiation facilities. It aims at
+ providing reading/writing different file formats, data reduction
+ routines and a set of Qt widgets to browse and visualize data.
+ .
+ The current version provides :
+ .
+ * reading HDF5 file format (with support of SPEC file format)
+ * histogramming
+ * fitting
+ * 1D and 2D visualization using multiple backends (matplotlib or OpenGL)
+ * image plot widget with a set of associated tools (See changelog file).
+ * Unified browser for HDF5, SPEC and image file formats supporting inspection
+ and visualization of n-dimensional datasets.
+ * Unified viewer (silx view filename) for HDF5, SPEC and image file formats
+ * OpenGL-based widget to display 3D scalar field with
+ isosurface and cutting plane.
+ .
+ This is the Python 3 version of the package.
+
+
+Package: python-silx-doc
+Architecture: all
+Section: doc
+Depends: libjs-mathjax, ${misc:Depends}, ${sphinxdoc:Depends}
+Description: Toolbox for X-Ray data analysis - Documentation
+ The silx project aims at providing a collection of Python packages to
+ support the development of data assessment, reduction and analysis
+ applications at synchrotron radiation facilities. It aims at
+ providing reading/writing different file formats, data reduction
+ routines and a set of Qt widgets to browse and visualize data.
+ .
+ The current version provides :
+ .
+ * reading HDF5 file format (with support of SPEC file format)
+ * histogramming
+ * fitting
+ * 1D and 2D visualization using multiple backends (matplotlib or OpenGL)
+ * image plot widget with a set of associated tools (See changelog file).
+ * Unified browser for HDF5, SPEC and image file formats supporting inspection
+ and visualization of n-dimensional datasets.
+ * Unified viewer (silx view filename) for HDF5, SPEC and image file formats
+ * OpenGL-based widget to display 3D scalar field with
+ isosurface and cutting plane.
+ .
+ This is the common documentation package.
diff --git a/package/debian8/gbp.conf b/package/debian10/gbp.conf
index f68d262..f68d262 100644
--- a/package/debian8/gbp.conf
+++ b/package/debian10/gbp.conf
diff --git a/package/debian10/patches/0002-use-the-system-mathjax-privacy-breach.patch b/package/debian10/patches/0002-use-the-system-mathjax-privacy-breach.patch
new file mode 100644
index 0000000..04deea7
--- /dev/null
+++ b/package/debian10/patches/0002-use-the-system-mathjax-privacy-breach.patch
@@ -0,0 +1,25 @@
+From: =?utf-8?q?Picca_Fr=C3=A9d=C3=A9ric-Emmanuel?=
+ <picca@synchrotron-soleil.fr>
+Date: Thu, 10 Aug 2017 10:19:39 +0200
+Subject: use the system mathjax (privacy breach)
+
+---
+ doc/source/conf.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/doc/source/conf.py b/doc/source/conf.py
+index 86dbccf..18bfce2 100644
+--- a/doc/source/conf.py
++++ b/doc/source/conf.py
+@@ -143,6 +143,11 @@ pygments_style = 'sphinx'
+ # A list of ignored prefixes for module index sorting.
+ # modindex_common_prefix = []
+
++# -- Option for MathJax extension ----------------------------------------------
++
++# Override required in order to use Debian's system mathjax
++mathjax_path = 'file:///usr/share/javascript/mathjax/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
++
+
+ # -- Options for HTML output ---------------------------------------------------
+
diff --git a/package/debian10/patches/0003-do-not-modify-PYTHONPATH-from-setup.py.patch b/package/debian10/patches/0003-do-not-modify-PYTHONPATH-from-setup.py.patch
new file mode 100644
index 0000000..e58e985
--- /dev/null
+++ b/package/debian10/patches/0003-do-not-modify-PYTHONPATH-from-setup.py.patch
@@ -0,0 +1,22 @@
+From: =?utf-8?q?Picca_Fr=C3=A9d=C3=A9ric-Emmanuel?= <picca@debian.org>
+Date: Sun, 4 Mar 2018 16:36:35 +0100
+Subject: do not modify PYTHONPATH from setup.py
+
+---
+ setup.py | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/setup.py b/setup.py
+index b63d0eb..334f7a8 100644
+--- a/setup.py
++++ b/setup.py
+@@ -260,7 +260,8 @@ class BuildMan(Command):
+ path.insert(0, os.path.abspath(build.build_lib))
+
+ env = dict((str(k), str(v)) for k, v in os.environ.items())
+- env["PYTHONPATH"] = os.pathsep.join(path)
++
++ # env["PYTHONPATH"] = os.pathsep.join(path)
+ if not os.path.isdir("build/man"):
+ os.makedirs("build/man")
+ import subprocess
diff --git a/package/debian10/patches/series b/package/debian10/patches/series
new file mode 100644
index 0000000..e3795b3
--- /dev/null
+++ b/package/debian10/patches/series
@@ -0,0 +1,2 @@
+0002-use-the-system-mathjax-privacy-breach.patch
+0003-do-not-modify-PYTHONPATH-from-setup.py.patch
diff --git a/package/debian10/py3dist-overrides b/package/debian10/py3dist-overrides
new file mode 100644
index 0000000..2c4ce13
--- /dev/null
+++ b/package/debian10/py3dist-overrides
@@ -0,0 +1 @@
+pyqt5 python3-pyqt5,python3-pyqt5.qtopengl,python3-pyqt5.qtsvg \ No newline at end of file
diff --git a/package/debian8/python-silx-doc.doc-base b/package/debian10/python-silx-doc.doc-base
index b290d8a..b290d8a 100644
--- a/package/debian8/python-silx-doc.doc-base
+++ b/package/debian10/python-silx-doc.doc-base
diff --git a/package/debian10/rules b/package/debian10/rules
new file mode 100755
index 0000000..b75711c
--- /dev/null
+++ b/package/debian10/rules
@@ -0,0 +1,85 @@
+#!/usr/bin/make -f
+
+# avoir bbuild FTBFS
+export HOME=$(CURDIR)/debian/tmp-home
+export XDG_RUNTIME_DIR=$(HOME)/runtime
+export POCL_CACHE_DIR=$(HOME)/.cache/
+
+export DEB_BUILD_MAINT_OPTIONS = hardening=+all
+export PYBUILD_AFTER_INSTALL=rm -rf {destdir}/usr/bin/
+export PYBUILD_NAME=silx
+export SPECFILE_USE_GNU_SOURCE=1
+export SILX_FULL_INSTALL_REQUIRES=1
+
+DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH)
+
+# Make does not offer a recursive wildcard function, so here's one:
+rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2))
+
+# How to recursively find all files with the same name in a given folder
+ALL_PYX := $(call rwildcard,silx/,*.pyx)
+#NOTA: No space before *
+
+# get the default python3 interpreter version
+PY3VER := $(shell py3versions -dv)
+
+%:
+ dh $@ --with python2,python3,sphinxdoc --buildsystem=pybuild
+
+override_dh_clean:
+ dh_clean
+ # remove the cython generated file to force rebuild
+ rm -f $(patsubst %.pyx,%.cpp,${ALL_PYX})
+ rm -f $(patsubst %.pyx,%.c,${ALL_PYX})
+ rm -f $(patsubst %.pyx,%.html,${ALL_PYX})
+ rm -rf doc/build/html
+ rm -rf build/man
+ rm -rf *.egg-info
+
+override_dh_auto_build:
+ dh_auto_build
+ # build man pages
+ dh_auto_build -- -s custom --build-args="env PYTHONPATH={build_dir} {interpreter} setup.py build_man"
+
+override_dh_install:
+ dh_numpy
+ dh_numpy3
+
+ # install scripts into silx
+ python3 setup.py install_scripts -d debian/silx/usr/bin
+ dh_install -p silx package/desktop/*.desktop usr/share/applications
+ dh_install -p silx package/desktop/silx.png usr/share/icons/hicolor/48x48/apps
+ dh_install -p silx package/desktop/silx.svg usr/share/icons/hicolor/scalable/apps
+ dh_install -p silx package/desktop/silx.xml usr/share/mime/packages
+
+ # install the qtdesigner files only for the python3 package
+ dh_install -p python3-silx qtdesigner_plugins/*.py /usr/lib/$(DEB_HOST_MULTIARCH)/qt5/plugins/designer/python
+
+ dh_install
+
+override_dh_python3:
+ dh_python3
+ dh_python3 -p python3-silx /usr/lib/$(DEB_HOST_MULTIARCH)/qt5/plugins/designer/python
+
+# WITH_QT_TEST=False to disable graphical tests
+# SILX_OPENCL=False to disable OpenCL tests
+# SILX_TEST_LOW_MEM=True to disable tests taking large amount of memory
+# GPU=False to disable the use of a GPU with OpenCL test
+# WITH_GL_TEST=False to disable tests using OpenGL
+# UNACTIVATED UNTIL dh_python from UNSTABLE IS FIXED
+# https://lists.debian.org/debian-python/2017/08/msg00095.html
+override_dh_auto_test:
+ mkdir -p $(POCL_CACHE_DIR) # create POCL cachedir in order to avoid an FTBFS in sbuild
+ dh_auto_test -- -s custom --test-args="env PYTHONPATH={build_dir} GPU=False WITH_QT_TEST=False SILX_OPENCL=False SILX_TEST_LAW_MEM=True xvfb-run -a --server-args=\"-screen 0 1024x768x24\" {interpreter} run_tests.py -vv --installed"
+
+override_dh_installman:
+ dh_installman -p silx build/man/*.1
+
+override_dh_sphinxdoc:
+ifeq (,$(findstring nodocs, $(DEB_BUILD_OPTIONS)))
+ #mkdir -p $(POCL_CACHE_DIR) # create POCL cachedir in order to avoid an FTBFS in sbuild
+ mkdir -p -m 700 $(XDG_RUNTIME_DIR)
+ pybuild --build -s custom -p $(PY3VER) --build-args="cd doc && env PYTHONPATH={build_dir} http_proxy='127.0.0.1:9' xvfb-run -a --server-args=\"-screen 0 1024x768x24\" {interpreter} -m sphinx -N -bhtml source build/html"
+ dh_installdocs "doc/build/html" -p python-silx-doc
+ dh_sphinxdoc -O--buildsystem=pybuild
+endif
diff --git a/package/debian8/source/format b/package/debian10/source/format
index 163aaf8..163aaf8 100644
--- a/package/debian8/source/format
+++ b/package/debian10/source/format
diff --git a/package/debian8/source/options b/package/debian10/source/options
index 6e88e49..6e88e49 100644
--- a/package/debian8/source/options
+++ b/package/debian10/source/options
diff --git a/package/debian10/tests/control b/package/debian10/tests/control
new file mode 100644
index 0000000..1e5cddf
--- /dev/null
+++ b/package/debian10/tests/control
@@ -0,0 +1,31 @@
+Test-Command: set -efu
+ ; for py in $(pyversions -r 2>/dev/null)
+ ; do cd "$AUTOPKGTEST_TMP"
+ ; echo "Testing with $py:"
+ ; xvfb-run -a --server-args="-screen 0 1024x768x24" $py -c "import silx.test; silx.test.run_tests()" 2>&1
+ ; done
+Depends: python-all, python-silx, xauth, xvfb
+
+Test-Command: set -efu
+ ; for py in $(pyversions -r 2>/dev/null)
+ ; do cd "$AUTOPKGTEST_TMP"
+ ; echo "Testing with $py-dbg:"
+ ; xvfb-run -a --server-args="-screen 0 1024x768x24" $py-dbg -c "import silx.test; silx.test.run_tests()" 2>&1
+ ; done
+Depends: python-all-dbg, python-silx-dbg, xauth, xvfb
+
+Test-Command: set -efu
+ ; for py in $(py3versions -r 2>/dev/null)
+ ; do cd "$AUTOPKGTEST_TMP"
+ ; echo "Testing with $py:"
+ ; xvfb-run -a --server-args="-screen 0 1024x768x24" $py -c "import silx.test; silx.test.run_tests()" 2>&1
+ ; done
+Depends: python3-all, python3-silx, xauth, xvfb
+
+Test-Command: set -efu
+ ; for py in $(py3versions -r 2>/dev/null)
+ ; do cd "$AUTOPKGTEST_TMP"
+ ; echo "Testing with $py-dbg:"
+ ; xvfb-run -a --server-args="-screen 0 1024x768x24" $py-dbg -c "import silx.test; silx.test.run_tests()" 2>&1
+ ; done
+Depends: python3-all-dbg, python3-silx-dbg, xauth, xvfb
diff --git a/package/debian10/watch b/package/debian10/watch
new file mode 100644
index 0000000..99444f9
--- /dev/null
+++ b/package/debian10/watch
@@ -0,0 +1,7 @@
+version=4
+opts=repacksuffix=+dfsg,\
+pgpsigurlmangle=s/$/.asc/,\
+dversionmangle=s/\+dfsg//,\
+uversionmangle=s/(rc|a|b|c)/~$1/ \
+https://pypi.python.org/packages/source/s/@PACKAGE@/ \
+ @PACKAGE@-@ANY_VERSION@@ARCHIVE_EXT@ debian uupdate
diff --git a/package/debian8/changelog b/package/debian8/changelog
deleted file mode 100644
index 763d069..0000000
--- a/package/debian8/changelog
+++ /dev/null
@@ -1,22 +0,0 @@
-silx (0.3.0-1) unstable; urgency=low
-
- * debian/control
- - Add missing dependencies
- - Add -dbg packages
- * Upstream version 0.4.0a
-
- -- Thomas Vincent <tvincent@edna-site.org> Mon, 23 Jan 2017 17:09:20 +0100
-
-silx (0.1.0-1) unstable; urgency=low
-
- * debian/control
- - Comment -dbg packages
- * Upstream version 0.1.0
-
- -- Thomas Vincent <thomas.vincent@esrf.fr> Thu, 14 Apr 2016 09:35:28 +0200
-
-silx (0.0.0-1) unstable; urgency=low
-
- * Initial release (Closes: #??????)
-
- -- Jerome Kieffer <jerome.kieffer@esrf.fr> Tue, 15 Mar 2016 11:00:20 +0100
diff --git a/package/debian8/clean b/package/debian8/clean
deleted file mode 100644
index 668a363..0000000
--- a/package/debian8/clean
+++ /dev/null
@@ -1 +0,0 @@
-*.egg-info/* \ No newline at end of file
diff --git a/package/debian8/compat b/package/debian8/compat
deleted file mode 100644
index ec63514..0000000
--- a/package/debian8/compat
+++ /dev/null
@@ -1 +0,0 @@
-9
diff --git a/package/debian8/control b/package/debian8/control
deleted file mode 100644
index 2e3fc58..0000000
--- a/package/debian8/control
+++ /dev/null
@@ -1,134 +0,0 @@
-Source: silx
-Maintainer: Debian Science Maintainers <debian-science-maintainers@lists.alioth.debian.org>
-Uploaders: Jerome Kieffer <jerome.kieffer@esrf.fr>,
- Picca Frédéric-Emmanuel <picca@debian.org>
-Section: science
-Priority: extra
-Build-Depends: cython,
- cython3,
- libstdc++-4.9-dev,
- libstdc++6,
- debhelper (>=9.20150101+deb8u2),
- dh-python,
- python-all-dev,
- python-numpy,
- python-fabio,
- python-h5py,
- python-pyopencl,
- python-mako,
- python-matplotlib,
- python-nbsphinx,
- python-dateutil,
- python-opengl,
- python-pyqt5,
- python-pyqt5.qtsvg,
- python-pyqt5.qtopengl,
- python-scipy,
- python-setuptools,
- python-six,
- python-sphinx,
- python-sphinxcontrib.programoutput,
- python-enum34,
- python-concurrent.futures,
- python3-all-dev,
- python3-numpy,
- python3-fabio,
- python3-h5py,
- python3-pyopencl,
- python3-mako,
- python3-matplotlib,
- python3-nbsphinx,
- python3-dateutil,
- python3-opengl,
- python3-pyqt5,
- python3-pyqt5.qtsvg,
- python3-pyqt5.qtopengl,
- python3-scipy,
- python3-setuptools,
- python3-six,
- python3-sphinx,
- python3-sphinxcontrib.programoutput,
- help2man,
- devscripts
-Standards-Version: 3.9.6
-Vcs-Browser: https://anonscm.debian.org/cgit/debian-science/packages/silx.git
-Vcs-Git: git://anonscm.debian.org/debian-science/packages/silx.git
-Homepage: https://github.com/silx-kit/silx
-X-Python-Version: >= 2.7
-X-Python3-Version: >= 3.4
-
-Package: silx
-Architecture: all
-Depends: ${misc:Depends},
- ${python:Depends},
- ${shlibs:Depends},
- python3-pkg-resources,
- python3-silx (>= ${source:Version})
-Description: Toolbox for X-Ray data analysis - Executables
- .
- This uses the Python 3 version of the package.
-
-Package: python-silx
-Architecture: any
-Section: python
-Depends: ${misc:Depends},
- ${python:Depends},
- ${shlibs:Depends},
- libstdc++6,
- python-numpy,
- python-fabio,
- python-h5py,
- python-pyopencl,
- python-mako,
- python-matplotlib,
- python-dateutil,
- python-opengl,
- python-pyqt5,
- python-pyqt5.qtsvg,
- python-pyqt5.qtopengl,
- python-scipy,
- python-setuptools,
- python-six,
- python-enum34,
- python-concurrent.futures,
-# Recommends:
-Suggests: python-rfoo
-Description: Toolbox for X-Ray data analysis - Python2 library
- .
- This is the Python 2 version of the package.
-
-Package: python3-silx
-Architecture: any
-Section: python
-Depends: ${misc:Depends},
- ${python3:Depends},
- ${shlibs:Depends},
- libstdc++6,
- python3-numpy,
- python3-fabio,
- python3-h5py,
- python3-pyopencl,
- python3-mako,
- python3-matplotlib,
- python3-dateutil,
- python3-opengl,
- python3-pyqt5,
- python3-pyqt5.qtsvg,
- python3-pyqt5.qtopengl,
- python3-scipy,
- python3-setuptools,
- python3-six,
-# Recommends:
-# Suggests: python3-rfoo
-Description: Toolbox for X-Ray data analysis - Python3
- .
- This is the Python 3 version of the package.
-
-Package: python-silx-doc
-Architecture: all
-Section: doc
-Depends: ${misc:Depends},
- ${sphinxdoc:Depends}
-Description: Toolbox for X-Ray data analysis - Documentation
- .
- This is the common documentation package.
diff --git a/package/debian8/rules b/package/debian8/rules
deleted file mode 100755
index 160147b..0000000
--- a/package/debian8/rules
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/usr/bin/make -f
-
-export PYBUILD_NAME=silx
-export SPECFILE_USE_GNU_SOURCE=1
-
-# Make does not offer a recursive wildcard function, so here's one:
-rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2))
-
-# How to recursively find all files with the same name in a given folder
-ALL_PYX := $(call rwildcard,silx/,*.pyx)
-#NOTA: No space before *
-
-%:
- dh $@ --with python2,python3 --buildsystem=pybuild
-
-override_dh_clean:
- dh_clean
- # remove the cython generated file to force rebuild
- rm -f $(patsubst %.pyx,%.cpp,${ALL_PYX})
- rm -f $(patsubst %.pyx,%.c,${ALL_PYX})
- rm -f $(patsubst %.pyx,%.html,${ALL_PYX})
- rm -rf build/html
- rm -rf *.egg-info
-
-override_dh_auto_build:
- dh_auto_build
- python setup.py build build_man build_doc
-
-override_dh_install:
- dh_numpy
- dh_numpy3
-
- # move the scripts to right package
- dh_install -p silx debian/python3-silx/usr/bin/* usr/bin
- dh_install -p silx package/desktop/*.desktop usr/share/applications
- dh_install -p silx package/desktop/silx.png usr/share/icons/hicolor/48x48/apps
- dh_install -p silx package/desktop/silx.svg usr/share/icons/hicolor/scalable/apps
- dh_install -p silx package/desktop/silx.xml usr/share/mime/packages
- rm -rf debian/python-silx/usr/bin
- rm -rf debian/python3-silx/usr/bin
-
- dh_install
-
-override_dh_auto_test:
- dh_auto_test -- -s custom --test-args="env PYTHONPATH={build_dir} WITH_QT_TEST=False {interpreter} run_tests.py -v"
-
-override_dh_installman:
- dh_installman -p silx build/man/*.1
-
-override_dh_installdocs:
- dh_installdocs "build/sphinx/html" -p python-silx-doc
- dh_installdocs
diff --git a/package/debian8/watch b/package/debian8/watch
deleted file mode 100644
index 8972716..0000000
--- a/package/debian8/watch
+++ /dev/null
@@ -1,5 +0,0 @@
-version=3
-opts=repacksuffix=+dfsg,\
-uversionmangle=s/(rc|a|b|c)/~$1/,\
-dversionmangle=s/\+dfsg// \
-http://pypi.debian.net/silx/silx-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz)))
diff --git a/package/debian9/control b/package/debian9/control
index d0de5fe..4abe0fe 100644
--- a/package/debian9/control
+++ b/package/debian9/control
@@ -63,7 +63,7 @@ X-Python3-Version: >= 3.4
Package: silx
Architecture: all
Depends: ${misc:Depends},
- ${python:Depends},
+ ${python3:Depends},
${shlibs:Depends},
python3-pkg-resources,
python3-silx (>= ${source:Version})
diff --git a/requirements.txt b/requirements.txt
index cdffb47..989e5b3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -23,6 +23,7 @@ PyOpenGL # For silx.gui.plot3d
python-dateutil # For silx.gui.plot
scipy # For silx.math.fit demo, silx.image.sift demo, silx.image.sift.test
Pillow # For silx.opencl.image.test
+Cython >= 0.21.1 # For silx.math, silx.io, silx.image
# PyQt5, PySide2 or PyQt4 # For silx.gui
# Try to install a Qt binding from a wheel
diff --git a/setup.py b/setup.py
index 1029bf0..86ae5bb 100644
--- a/setup.py
+++ b/setup.py
@@ -118,6 +118,7 @@ classifiers = ["Development Status :: 4 - Beta",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Scientific/Engineering :: Physics",
"Topic :: Software Development :: Libraries :: Python Modules",
@@ -431,26 +432,24 @@ class Build(_build):
"do not use OpenMP for compiled extension modules"),
('openmp', None,
"use OpenMP for the compiled extension modules"),
- ('no-cython', None,
- "do not compile Cython extension modules (use default compiled c-files)"),
('force-cython', None,
"recompile all Cython extension modules"),
]
user_options.extend(_build.user_options)
- boolean_options = ['no-openmp', 'openmp', 'no-cython', 'force-cython']
+ boolean_options = ['no-openmp', 'openmp', 'force-cython']
boolean_options.extend(_build.boolean_options)
def initialize_options(self):
_build.initialize_options(self)
self.no_openmp = None
self.openmp = None
- self.no_cython = None
self.force_cython = None
def finalize_options(self):
_build.finalize_options(self)
- self.finalize_cython_options(min_version='0.21.1')
+ if not self.force_cython:
+ self.force_cython = self._parse_env_as_bool("FORCE_CYTHON") is True
self.finalize_openmp_options()
def _parse_env_as_bool(self, key):
@@ -477,9 +476,9 @@ class Build(_build):
elif self.no_openmp:
use_openmp = False
else:
- env_force_cython = self._parse_env_as_bool("WITH_OPENMP")
- if env_force_cython is not None:
- use_openmp = env_force_cython
+ env_with_openmp = self._parse_env_as_bool("WITH_OPENMP")
+ if env_with_openmp is not None:
+ use_openmp = env_with_openmp
else:
# Use it by default
use_openmp = True
@@ -498,49 +497,6 @@ class Build(_build):
del self.openmp
self.use_openmp = use_openmp
- def finalize_cython_options(self, min_version=None):
- """
- Check if cythonization must be used for the extensions.
-
- The result is stored into the object.
- """
-
- if self.force_cython:
- use_cython = "force"
- elif self.no_cython:
- use_cython = "no"
- else:
- env_force_cython = self._parse_env_as_bool("FORCE_CYTHON")
- env_with_cython = self._parse_env_as_bool("WITH_CYTHON")
- if env_force_cython is True:
- use_cython = "force"
- elif env_with_cython is True:
- use_cython = "yes"
- elif env_with_cython is False:
- use_cython = "no"
- else:
- # Use it by default
- use_cython = "yes"
-
- if use_cython in ["force", "yes"]:
- try:
- import Cython.Compiler.Version
- if min_version and Cython.Compiler.Version.version < min_version:
- msg = "Cython version is too old. At least version is %s \
- expected. Cythonization is skipped."
- logger.warning(msg, str(min_version))
- use_cython = "no"
- except ImportError:
- msg = "Cython is not available. Cythonization is skipped."
- logger.warning(msg)
- use_cython = "no"
-
- # Remove attribute used by distutils parsing
- # use 'use_cython' and 'force_cython' instead
- del self.no_cython
- self.force_cython = use_cython == "force"
- self.use_cython = use_cython in ["force", "yes"]
-
class BuildExt(build_ext):
"""Handle extension compilation.
@@ -562,34 +518,8 @@ class BuildExt(build_ext):
build_ext.finalize_options(self)
build_obj = self.distribution.get_command_obj("build")
self.use_openmp = build_obj.use_openmp
- self.use_cython = build_obj.use_cython
self.force_cython = build_obj.force_cython
- def patch_with_default_cythonized_files(self, ext):
- """Replace cython files by .c or .cpp files in extension's sources.
-
- It replaces the *.pyx and *.py source files of the extensions
- to either *.cpp or *.c source files.
- No compilation is performed.
-
- :param Extension ext: An extension to patch.
- """
- new_sources = []
- for source in ext.sources:
- base, file_ext = os.path.splitext(source)
- if file_ext in ('.pyx', '.py'):
- if ext.language == 'c++':
- cythonized = base + '.cpp'
- else:
- cythonized = base + '.c'
- if not os.path.isfile(cythonized):
- raise RuntimeError("Source file not found: %s. Cython is needed" % cythonized)
- print("Use default cythonized file for %s" % source)
- new_sources.append(cythonized)
- else:
- new_sources.append(source)
- ext.sources = new_sources
-
def patch_extension(self, ext):
"""
Patch an extension according to requested Cython and OpenMP usage.
@@ -597,17 +527,14 @@ class BuildExt(build_ext):
:param Extension ext: An extension
"""
# Cytonize
- if not self.use_cython:
- self.patch_with_default_cythonized_files(ext)
- else:
- from Cython.Build import cythonize
- patched_exts = cythonize(
- [ext],
- compiler_directives={'embedsignature': True,
- 'language_level': 3},
- force=self.force_cython
- )
- ext.sources = patched_exts[0].sources
+ from Cython.Build import cythonize
+ patched_exts = cythonize(
+ [ext],
+ compiler_directives={'embedsignature': True,
+ 'language_level': 3},
+ force=self.force_cython
+ )
+ ext.sources = patched_exts[0].sources
# Remove OpenMP flags if OpenMP is disabled
if not self.use_openmp:
@@ -773,37 +700,6 @@ class CleanCommand(Clean):
except OSError:
pass
-################################################################################
-# Source tree
-################################################################################
-
-class SourceDistWithCython(sdist):
- """
- Force cythonization of the extensions before generating the source
- distribution.
-
- To provide the widest compatibility the cythonized files are provided
- without suppport of OpenMP.
- """
-
- description = "Create a source distribution including cythonized files (tarball, zip file, etc.)"
-
- def finalize_options(self):
- sdist.finalize_options(self)
- self.extensions = self.distribution.ext_modules
-
- def run(self):
- self.cythonize_extensions()
- sdist.run(self)
-
- def cythonize_extensions(self):
- from Cython.Build import cythonize
- cythonize(
- self.extensions,
- compiler_directives={'embedsignature': True,
- 'language_level': 3},
- force=True
- )
################################################################################
# Debian source tree
@@ -904,7 +800,7 @@ def get_project_configuration(dry_run):
install_requires.append("enum34")
install_requires.append("futures")
- setup_requires = ["setuptools", "numpy>=1.12"]
+ setup_requires = ["setuptools", "numpy>=1.12", "Cython>=0.21.1"]
# extras requirements: target 'full' to install all dependencies at once
full_requires = [
@@ -964,7 +860,6 @@ def get_project_configuration(dry_run):
build_ext=BuildExt,
build_man=BuildMan,
clean=CleanCommand,
- sdist=SourceDistWithCython,
debian_src=sdist_debian)
if dry_run:
diff --git a/silx.egg-info/PKG-INFO b/silx.egg-info/PKG-INFO
index 1f1f36b..9c1e298 100644
--- a/silx.egg-info/PKG-INFO
+++ b/silx.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: silx
-Version: 0.11.0
+Version: 0.12.0b0
Summary: Software library for X-ray data analysis
Home-page: http://www.silx.org/
Author: data analysis unit
@@ -133,6 +133,7 @@ Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Scientific/Engineering :: Physics
Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/silx.egg-info/SOURCES.txt b/silx.egg-info/SOURCES.txt
index 024a121..30434a4 100644
--- a/silx.egg-info/SOURCES.txt
+++ b/silx.egg-info/SOURCES.txt
@@ -277,10 +277,15 @@ doc/source/modules/utils/weakref.rst
doc/source/sample_code/index.rst
doc/source/sample_code/img/animatedicons.png
doc/source/sample_code/img/colormapDialog.png
+doc/source/sample_code/img/compareImages.png
+doc/source/sample_code/img/compositeline.png
doc/source/sample_code/img/customDataView.png
doc/source/sample_code/img/customHdf5TreeModel.png
+doc/source/sample_code/img/dropZones.png
+doc/source/sample_code/img/exampleBaseline.png
doc/source/sample_code/img/fftPlotAction.png
doc/source/sample_code/img/fileDialog.png
+doc/source/sample_code/img/findContours.png
doc/source/sample_code/img/hdf5widget.png
doc/source/sample_code/img/icons.png
doc/source/sample_code/img/imageview.png
@@ -290,25 +295,31 @@ doc/source/sample_code/img/plot3dSceneWindow.png
doc/source/sample_code/img/plot3dUpdateScatterFromThread.png
doc/source/sample_code/img/plotClearAction.png
doc/source/sample_code/img/plotContextMenu.png
+doc/source/sample_code/img/plotCurveLegendWidget.png
doc/source/sample_code/img/plotInteractiveImageROI.png
doc/source/sample_code/img/plotItemsSelector.png
doc/source/sample_code/img/plotLimits.png
+doc/source/sample_code/img/plotStats.png
doc/source/sample_code/img/plotUpdateCurveFromThread.png
doc/source/sample_code/img/plotUpdateImageFromThread.png
doc/source/sample_code/img/plotWidget.png
doc/source/sample_code/img/printPreview.png
doc/source/sample_code/img/scatterMask.png
+doc/source/sample_code/img/scatterview.png
doc/source/sample_code/img/shiftPlotAction.png
doc/source/sample_code/img/simplewidget.png
doc/source/sample_code/img/stackView.png
+doc/source/sample_code/img/syncPlotLocation.png
doc/source/sample_code/img/syncaxis.png
doc/source/sample_code/img/viewer3DVolume.png
examples/__init__.py
examples/colormapDialog.py
examples/compareImages.py
+examples/compositeline.py
examples/customDataView.py
examples/customHdf5TreeModel.py
examples/dropZones.py
+examples/exampleBaseline.py
examples/fft.png
examples/fftPlotAction.py
examples/fileDialog.py
@@ -332,6 +343,7 @@ examples/plotUpdateImageFromThread.py
examples/plotWidget.py
examples/printPreview.py
examples/scatterMask.py
+examples/scatterview.py
examples/shiftPlotAction.py
examples/simplewidget.py
examples/stackView.py
@@ -339,16 +351,20 @@ examples/syncPlotLocation.py
examples/syncaxis.py
examples/viewer3DVolume.py
examples/writetoh5.py
-package/debian8/changelog
-package/debian8/clean
-package/debian8/compat
-package/debian8/control
-package/debian8/gbp.conf
-package/debian8/python-silx-doc.doc-base
-package/debian8/rules
-package/debian8/watch
-package/debian8/source/format
-package/debian8/source/options
+package/debian10/changelog
+package/debian10/compat
+package/debian10/control
+package/debian10/gbp.conf
+package/debian10/py3dist-overrides
+package/debian10/python-silx-doc.doc-base
+package/debian10/rules
+package/debian10/watch
+package/debian10/patches/0002-use-the-system-mathjax-privacy-breach.patch
+package/debian10/patches/0003-do-not-modify-PYTHONPATH-from-setup.py.patch
+package/debian10/patches/series
+package/debian10/source/format
+package/debian10/source/options
+package/debian10/tests/control
package/debian9/changelog
package/debian9/clean
package/debian9/compat
@@ -542,6 +558,7 @@ silx/gui/plot/backends/glutils/GLTexture.py
silx/gui/plot/backends/glutils/PlotImageFile.py
silx/gui/plot/backends/glutils/__init__.py
silx/gui/plot/items/__init__.py
+silx/gui/plot/items/_pick.py
silx/gui/plot/items/axis.py
silx/gui/plot/items/complex.py
silx/gui/plot/items/core.py
@@ -675,15 +692,21 @@ silx/gui/utils/__init__.py
silx/gui/utils/concurrent.py
silx/gui/utils/image.py
silx/gui/utils/projecturl.py
+silx/gui/utils/qtutils.py
silx/gui/utils/testutils.py
silx/gui/utils/test/__init__.py
+silx/gui/utils/test/test.py
silx/gui/utils/test/test_async.py
silx/gui/utils/test/test_image.py
+silx/gui/utils/test/test_qtutils.py
+silx/gui/utils/test/test_testutils.py
silx/gui/widgets/BoxLayoutDockWidget.py
+silx/gui/widgets/ColormapNameComboBox.py
silx/gui/widgets/FloatEdit.py
silx/gui/widgets/FlowLayout.py
silx/gui/widgets/FrameBrowser.py
silx/gui/widgets/HierarchicalTableView.py
+silx/gui/widgets/LegendIconWidget.py
silx/gui/widgets/MedianFilterDialog.py
silx/gui/widgets/PeriodicTable.py
silx/gui/widgets/PrintGeometryDialog.py
@@ -707,19 +730,17 @@ silx/gui/widgets/test/test_tablewidget.py
silx/gui/widgets/test/test_threadpoolpushbutton.py
silx/image/__init__.py
silx/image/backprojection.py
-silx/image/bilinear.c
silx/image/bilinear.pyx
silx/image/medianfilter.py
silx/image/phantomgenerator.py
silx/image/projection.py
silx/image/reconstruction.py
silx/image/setup.py
-silx/image/shapes.c
silx/image/shapes.pyx
silx/image/sift.py
silx/image/tomography.py
+silx/image/utils.py
silx/image/marchingsquares/__init__.py
-silx/image/marchingsquares/_mergeimpl.cpp
silx/image/marchingsquares/_mergeimpl.pyx
silx/image/marchingsquares/_skimage.py
silx/image/marchingsquares/setup.py
@@ -741,7 +762,6 @@ silx/io/fabioh5.py
silx/io/octaveh5.py
silx/io/rawh5.py
silx/io/setup.py
-silx/io/specfile.c
silx/io/specfile.pyx
silx/io/specfile_wrapper.pxd
silx/io/specfilewrapper.py
@@ -783,17 +803,13 @@ silx/io/test/test_url.py
silx/io/test/test_utils.py
silx/math/__init__.py
silx/math/calibration.py
-silx/math/chistogramnd.c
silx/math/chistogramnd.pyx
-silx/math/chistogramnd_lut.c
silx/math/chistogramnd_lut.pyx
-silx/math/colormap.c
silx/math/colormap.pyx
-silx/math/combo.c
silx/math/combo.pyx
silx/math/histogram.py
silx/math/histogramnd_c.pxd
-silx/math/marchingcubes.cpp
+silx/math/interpolate.pyx
silx/math/marchingcubes.pyx
silx/math/math_compatibility.pxd
silx/math/mc.pxd
@@ -810,17 +826,14 @@ silx/math/fft/test/__init__.py
silx/math/fft/test/test_fft.py
silx/math/fit/__init__.py
silx/math/fit/bgtheories.py
-silx/math/fit/filters.c
silx/math/fit/filters.pyx
silx/math/fit/filters_wrapper.pxd
silx/math/fit/fitmanager.py
silx/math/fit/fittheories.py
silx/math/fit/fittheory.py
-silx/math/fit/functions.c
silx/math/fit/functions.pyx
silx/math/fit/functions_wrapper.pxd
silx/math/fit/leastsq.py
-silx/math/fit/peaks.c
silx/math/fit/peaks.pyx
silx/math/fit/peaks_wrapper.pxd
silx/math/fit/setup.py
@@ -851,7 +864,6 @@ silx/math/marchingcubes/mc.hpp
silx/math/marchingcubes/mc_lut.cpp
silx/math/medianfilter/__init__.py
silx/math/medianfilter/median_filter.pxd
-silx/math/medianfilter/medianfilter.cpp
silx/math/medianfilter/medianfilter.pyx
silx/math/medianfilter/setup.py
silx/math/medianfilter/include/median_filter.hpp
@@ -868,6 +880,7 @@ silx/math/test/test_combo.py
silx/math/test/test_histogramnd_error.py
silx/math/test/test_histogramnd_nominal.py
silx/math/test/test_histogramnd_vs_np.py
+silx/math/test/test_interpolate.py
silx/math/test/test_marchingcubes.py
silx/opencl/__init__.py
silx/opencl/backprojection.py
@@ -923,6 +936,7 @@ silx/opencl/test/test_projection.py
silx/opencl/test/test_sparse.py
silx/opencl/test/test_stats.py
silx/resources/__init__.py
+silx/resources/gui/colormaps/cividis.npy
silx/resources/gui/colormaps/inferno.npy
silx/resources/gui/colormaps/magma.npy
silx/resources/gui/colormaps/plasma.npy
@@ -1017,6 +1031,18 @@ silx/resources/gui/icons/cube-top.png
silx/resources/gui/icons/cube-top.svg
silx/resources/gui/icons/cube.png
silx/resources/gui/icons/cube.svg
+silx/resources/gui/icons/description-description.png
+silx/resources/gui/icons/description-description.svg
+silx/resources/gui/icons/description-error.png
+silx/resources/gui/icons/description-error.svg
+silx/resources/gui/icons/description-name.png
+silx/resources/gui/icons/description-name.svg
+silx/resources/gui/icons/description-program.png
+silx/resources/gui/icons/description-program.svg
+silx/resources/gui/icons/description-title.png
+silx/resources/gui/icons/description-title.svg
+silx/resources/gui/icons/description-value.png
+silx/resources/gui/icons/description-value.svg
silx/resources/gui/icons/document-open.png
silx/resources/gui/icons/document-open.svg
silx/resources/gui/icons/document-print.png
@@ -1341,7 +1367,6 @@ silx/third_party/scipy_spatial.py
silx/third_party/setup.py
silx/third_party/_local/__init__.py
silx/third_party/_local/scipy_spatial/__init__.py
-silx/third_party/_local/scipy_spatial/qhull.c
silx/third_party/_local/scipy_spatial/qhull.pxd
silx/third_party/_local/scipy_spatial/qhull.pyx
silx/third_party/_local/scipy_spatial/qhull_misc.h
@@ -1405,4 +1430,5 @@ silx/utils/test/test_launcher.py
silx/utils/test/test_launcher_command.py
silx/utils/test/test_number.py
silx/utils/test/test_proxy.py
+silx/utils/test/test_testutils.py
silx/utils/test/test_weakref.py \ No newline at end of file
diff --git a/silx/app/view/Viewer.py b/silx/app/view/Viewer.py
index d543352..2daa2df 100644
--- a/silx/app/view/Viewer.py
+++ b/silx/app/view/Viewer.py
@@ -134,8 +134,10 @@ class Viewer(qt.QMainWindow):
treeModel = self.__treeview.findHdf5TreeModel()
columns = list(treeModel.COLUMN_IDS)
- columns.remove(treeModel.DESCRIPTION_COLUMN)
+ columns.remove(treeModel.VALUE_COLUMN)
columns.remove(treeModel.NODE_COLUMN)
+ columns.remove(treeModel.DESCRIPTION_COLUMN)
+ columns.insert(1, treeModel.DESCRIPTION_COLUMN)
self.__treeview.header().setSections(columns)
self._iconUpward = icons.getQIcon('plot-yup')
diff --git a/silx/gui/_glutils/OpenGLWidget.py b/silx/gui/_glutils/OpenGLWidget.py
index 7f600a0..c5ece9c 100644
--- a/silx/gui/_glutils/OpenGLWidget.py
+++ b/silx/gui/_glutils/OpenGLWidget.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2017 European Synchrotron Radiation Facility
+# Copyright (c) 2017-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -30,7 +30,7 @@ across Qt<=5.3 QtOpenGL.QGLWidget and QOpenGLWidget.
__authors__ = ["T. Vincent"]
__license__ = "MIT"
-__date__ = "26/07/2017"
+__date__ = "22/11/2019"
import logging
@@ -192,7 +192,12 @@ else:
# Check OpenGL version
if self.getOpenGLVersion() >= self.getRequestedOpenGLVersion():
- version = gl.glGetString(gl.GL_VERSION)
+ try:
+ gl.glGetError() # clear any previous error (if any)
+ version = gl.glGetString(gl.GL_VERSION)
+ except:
+ version = None
+
if version:
self.__isValid = True
else:
diff --git a/silx/gui/colors.py b/silx/gui/colors.py
index aa2958a..365b569 100644..100755
--- a/silx/gui/colors.py
+++ b/silx/gui/colors.py
@@ -97,6 +97,7 @@ _AVAILABLE_LUTS = collections.OrderedDict([
('blue', _LUT_DESCRIPTION('builtin', 'yellow', True)),
('jet', _LUT_DESCRIPTION('matplotlib', 'pink', True)),
('viridis', _LUT_DESCRIPTION('resource', 'pink', True)),
+ ('cividis', _LUT_DESCRIPTION('resource', 'pink', True)),
('magma', _LUT_DESCRIPTION('resource', 'green', True)),
('inferno', _LUT_DESCRIPTION('resource', 'green', True)),
('plasma', _LUT_DESCRIPTION('resource', 'green', True)),
@@ -116,10 +117,11 @@ DEFAULT_MAX_LOG = 10
def rgba(color, colorDict=None):
- """Convert color code '#RRGGBB' and '#RRGGBBAA' to (R, G, B, A)
+ """Convert color code '#RRGGBB' and '#RRGGBBAA' to a tuple (R, G, B, A)
+ of floats.
- It also convert RGB(A) values from uint8 to float in [0, 1] and
- accept a QColor as color argument.
+ It also supports RGB(A) from uint8 in [0, 255], float in [0, 1], and
+ QColor as color argument.
:param str color: The color to convert
:param dict colorDict: A dictionary of color name conversion to color code
@@ -167,8 +169,8 @@ def greyed(color, colorDict=None):
"""Convert color code '#RRGGBB' and '#RRGGBBAA' to a grey color
(R, G, B, A).
- It also convert RGB(A) values from uint8 to float in [0, 1] and
- accept a QColor as color argument.
+ It also supports RGB(A) from uint8 in [0, 255], float in [0, 1], and
+ QColor as color argument.
:param str color: The color to convert
:param dict colorDict: A dictionary of color name conversion to color code
@@ -180,6 +182,19 @@ def greyed(color, colorDict=None):
return g, g, g, a
+def asQColor(color):
+ """Convert color code '#RRGGBB' and '#RRGGBBAA' to a `qt.QColor`.
+
+ It also supports RGB(A) from uint8 in [0, 255], float in [0, 1], and
+ QColor as color argument.
+
+ :param str color: The color to convert
+ :rtype: qt.QColor
+ """
+ color = rgba(color)
+ return qt.QColor.fromRgbF(*color)
+
+
def cursorColorForColormap(colormapName):
"""Get a color suitable for overlay over a colormap.
@@ -386,7 +401,7 @@ class Colormap(qt.QObject):
def setFromColormap(self, other):
"""Set this colormap using information from the `other` colormap.
- :param Colormap other: Colormap to use as reference.
+ :param ~silx.gui.colors.Colormap other: Colormap to use as reference.
"""
if not self.isEditable():
raise NotEditableError('Colormap is not editable')
diff --git a/silx/gui/data/DataViewer.py b/silx/gui/data/DataViewer.py
index b33a931..67db5f9 100644
--- a/silx/gui/data/DataViewer.py
+++ b/silx/gui/data/DataViewer.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2016-2018 European Synchrotron Radiation Facility
+# Copyright (c) 2016-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -31,8 +31,10 @@ from silx.gui.data import DataViews
from silx.gui.data.DataViews import _normalizeData
import logging
from silx.gui import qt
+from silx.gui.utils import blockSignals
from silx.gui.data.NumpyAxesSelector import NumpyAxesSelector
+
__authors__ = ["V. Valls"]
__license__ = "MIT"
__date__ = "12/02/2019"
@@ -197,25 +199,38 @@ class DataViewer(qt.QFrame):
"""
Update the numpy-selector according to the needed axis names
"""
- previous = self.__numpySelection.blockSignals(True)
- self.__numpySelection.clear()
- info = self._getInfo()
- axisNames = self.__currentView.axesNames(self.__data, info)
- if info.isArray and info.size != 0 and self.__data is not None and axisNames is not None:
- self.__useAxisSelection = True
- self.__numpySelection.setAxisNames(axisNames)
- self.__numpySelection.setCustomAxis(self.__currentView.customAxisNames())
- data = self.normalizeData(self.__data)
- self.__numpySelection.setData(data)
- if hasattr(data, "shape"):
- isVisible = not (len(axisNames) == 1 and len(data.shape) == 1)
+ with blockSignals(self.__numpySelection):
+ previousPermutation = self.__numpySelection.permutation()
+ previousSelection = self.__numpySelection.selection()
+
+ self.__numpySelection.clear()
+
+ info = self._getInfo()
+ axisNames = self.__currentView.axesNames(self.__data, info)
+ if (info.isArray and info.size != 0 and
+ self.__data is not None and axisNames is not None):
+ self.__useAxisSelection = True
+ self.__numpySelection.setAxisNames(axisNames)
+ self.__numpySelection.setCustomAxis(
+ self.__currentView.customAxisNames())
+ data = self.normalizeData(self.__data)
+ self.__numpySelection.setData(data)
+
+ # Try to restore previous permutation and selection
+ try:
+ self.__numpySelection.setSelection(
+ previousSelection, previousPermutation)
+ except ValueError as e:
+ _logger.error("Not restoring selection because: %s", e)
+
+ if hasattr(data, "shape"):
+ isVisible = not (len(axisNames) == 1 and len(data.shape) == 1)
+ else:
+ isVisible = True
+ self.__axisSelection.setVisible(isVisible)
else:
- isVisible = True
- self.__axisSelection.setVisible(isVisible)
- else:
- self.__useAxisSelection = False
- self.__axisSelection.setVisible(False)
- self.__numpySelection.blockSignals(previous)
+ self.__useAxisSelection = False
+ self.__axisSelection.setVisible(False)
def __updateDataInView(self):
"""
diff --git a/silx/gui/data/DataViews.py b/silx/gui/data/DataViews.py
index 664090d..eb635c4 100644
--- a/silx/gui/data/DataViews.py
+++ b/silx/gui/data/DataViews.py
@@ -1482,7 +1482,8 @@ class _NXdataXYVScatterView(DataView):
data = self.normalizeData(data)
if info.hasNXdata and not info.isInvalidNXdata:
if nxdata.get_default(data, validate=False).is_x_y_value_scatter:
- return 100
+ # It have to be a little more than a NX curve priority
+ return 110
return DataView.UNSUPPORTED
diff --git a/silx/gui/data/NumpyAxesSelector.py b/silx/gui/data/NumpyAxesSelector.py
index 4530aa9..e6da0d4 100644
--- a/silx/gui/data/NumpyAxesSelector.py
+++ b/silx/gui/data/NumpyAxesSelector.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2016-2018 European Synchrotron Radiation Facility
+# Copyright (c) 2016-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -31,13 +31,18 @@ __authors__ = ["V. Valls"]
__license__ = "MIT"
__date__ = "29/01/2018"
+import logging
import numpy
import functools
from silx.gui.widgets.FrameBrowser import HorizontalSliderWithBrowser
from silx.gui import qt
+from silx.gui.utils import blockSignals
import silx.utils.weakref
+_logger = logging.getLogger(__name__)
+
+
class _Axis(qt.QWidget):
"""Widget displaying an axis.
@@ -110,6 +115,8 @@ class _Axis(qt.QWidget):
if axisName == "" and self.__axes.count() == 0:
self.__axes.setCurrentIndex(-1)
self.__updateSliderVisibility()
+ return
+
for index in range(self.__axes.count()):
name = self.__axes.itemData(index)
if name == axisName:
@@ -121,7 +128,7 @@ class _Axis(qt.QWidget):
def axisName(self):
"""Returns the selected axis name.
- If no names are selected, an empty string is retruned.
+ If no name is selected, an empty string is returned.
:rtype: str
"""
@@ -136,11 +143,11 @@ class _Axis(qt.QWidget):
:param List[str] axesNames: List of available names
"""
self.__axes.clear()
- previous = self.__axes.blockSignals(True)
- self.__axes.addItem(" ", "")
- for axis in axesNames:
- self.__axes.addItem(axis, axis)
- self.__axes.blockSignals(previous)
+ with blockSignals(self.__axes):
+ self.__axes.addItem(" ", "")
+ for axis in axesNames:
+ self.__axes.addItem(axis, axis)
+
self.__updateSliderVisibility()
def setCustomAxis(self, axesNames):
@@ -168,12 +175,19 @@ class _Axis(qt.QWidget):
self.__slider.setVisible(isVisible)
def value(self):
- """Returns the current selected position in the axis.
+ """Returns the currently selected position in the axis.
:rtype: int
"""
return self.__slider.value()
+ def setValue(self, value):
+ """Set the currently selected position in the axis.
+
+ :param int value:
+ """
+ self.__slider.setValue(value)
+
def __sliderValueChanged(self, value):
"""Called when the selected position in the axis change.
@@ -183,18 +197,14 @@ class _Axis(qt.QWidget):
def setNamedAxisSelectorVisibility(self, visible):
"""Hide or show the named axis combobox.
- If both the selector and the slider are hidden,
- hide the entire widget.
+
+ If both the selector and the slider are hidden, hide the entire widget.
:param visible: boolean
"""
self.__axes.setVisible(visible)
name = self.axisName()
-
- if not visible and name != "":
- self.setVisible(False)
- else:
- self.setVisible(True)
+ self.setVisible(visible or name == "")
class NumpyAxesSelector(qt.QWidget):
@@ -236,7 +246,6 @@ class NumpyAxesSelector(qt.QWidget):
self.__data = None
self.__selectedData = None
- self.__selection = tuple()
self.__axis = []
self.__axisNames = []
self.__customAxisNames = set([])
@@ -268,13 +277,12 @@ class NumpyAxesSelector(qt.QWidget):
if delta < 0:
delta = 0
for index, axis in enumerate(self.__axis):
- previous = axis.blockSignals(True)
- axis.setAxisNames(self.__axisNames)
- if index >= delta and index - delta < len(self.__axisNames):
- axis.setAxisName(self.__axisNames[index - delta])
- else:
- axis.setAxisName("")
- axis.blockSignals(previous)
+ with blockSignals(axis):
+ axis.setAxisNames(self.__axisNames)
+ if index >= delta and index - delta < len(self.__axisNames):
+ axis.setAxisName(self.__axisNames[index - delta])
+ else:
+ axis.setAxisName("")
self.__updateSelectedData()
def setCustomAxis(self, axesNames):
@@ -372,9 +380,8 @@ class NumpyAxesSelector(qt.QWidget):
# If there is no other solution we set the name at the same place
axisChanged = False
availableWidget = axis
- previous = availableWidget.blockSignals(True)
- availableWidget.setAxisName(missingName)
- availableWidget.blockSignals(previous)
+ with blockSignals(availableWidget):
+ availableWidget.setAxisName(missingName)
else:
# there is a duplicated name somewhere
# we swap it with the missing name or with nothing
@@ -387,9 +394,8 @@ class NumpyAxesSelector(qt.QWidget):
break
if missingName is None:
missingName = ""
- previous = dupWidget.blockSignals(True)
- dupWidget.setAxisName(missingName)
- dupWidget.blockSignals(previous)
+ with blockSignals(dupWidget):
+ dupWidget.setAxisName(missingName)
if self.__data is None:
return
@@ -402,70 +408,164 @@ class NumpyAxesSelector(qt.QWidget):
It fires a `selectionChanged` event.
"""
- if self.__data is None:
+ permutation = self.permutation()
+
+ if self.__data is None or permutation is None:
+ # No data or not all the expected axes are there
if self.__selectedData is not None:
self.__selectedData = None
- self.__selection = tuple()
self.selectionChanged.emit()
return
- selection = []
- axisNames = []
- for slider in self.__axis:
- name = slider.axisName()
- if name == "":
- selection.append(slider.value())
- else:
- selection.append(slice(None))
- axisNames.append(name)
- self.__selection = tuple(selection)
# get a view with few fixed dimensions
# with a h5py dataset, it create a copy
# TODO we can reuse the same memory in case of a copy
- view = self.__data[self.__selection]
-
- if set(self.__axisNames) - set(axisNames) != set([]):
- # Not all the expected axis are there
- if self.__selectedData is not None:
- self.__selectedData = None
- self.__selection = tuple()
- self.selectionChanged.emit()
- return
-
- # order axis as expected
- source = []
- destination = []
- order = []
- for index, name in enumerate(self.__axisNames):
- destination.append(index)
- source.append(axisNames.index(name))
- for _, s in sorted(zip(destination, source)):
- order.append(s)
- view = numpy.transpose(view, order)
-
- self.__selectedData = view
+ self.__selectedData = numpy.transpose(self.__data[self.selection()], permutation)
self.selectionChanged.emit()
def data(self):
"""Returns the input data.
- :rtype: numpy.ndarray
+ :rtype: Union[numpy.ndarray,None]
"""
- return self.__data
+ if self.__data is None:
+ return None
+ else:
+ return numpy.array(self.__data, copy=False)
def selectedData(self):
"""Returns the output data.
- :rtype: numpy.ndarray
+ This is equivalent to::
+
+ numpy.transpose(self.data()[self.selection()], self.permutation())
+
+ :rtype: Union[numpy.ndarray,None]
"""
- return self.__selectedData
+ if self.__selectedData is None:
+ return None
+ else:
+ return numpy.array(self.__selectedData, copy=False)
+
+ def permutation(self):
+ """Returns the axes permutation to convert data subset to selected data.
+
+ If permutation cannot be computer, it returns None.
+
+ :rtype: Union[List[int],None]
+ """
+ if self.__data is None:
+ return None
+ else:
+ indices = []
+ for name in self.__axisNames:
+ index = 0
+ for axis in self.__axis:
+ if axis.axisName() == name:
+ indices.append(index)
+ break
+ if axis.axisName() != "":
+ index += 1
+ else:
+ _logger.warning("No axis corresponding to: %s", name)
+ return None
+ return tuple(indices)
def selection(self):
"""Returns the selection tuple used to slice the data.
:rtype: tuple
"""
- return self.__selection
+ if self.__data is None:
+ return tuple()
+ else:
+ return tuple([axis.value() if axis.axisName() == "" else slice(None)
+ for axis in self.__axis])
+
+ def setSelection(self, selection, permutation=None):
+ """Set the selection along each dimension.
+
+ tuple returned by :meth:`selection` can be provided as input,
+ provided that it is for the same the number of axes and
+ the same number of dimensions of the data.
+
+ :param List[Union[int,slice,None]] selection:
+ The selection tuple with as one element for each dimension of the data.
+ If an element is None, then the whole dimension is selected.
+ :param Union[List[int],None] permutation:
+ The data axes indices to transpose.
+ If not given, no permutation is applied
+ :raise ValueError:
+ When the selection does not match current data shape and number of axes.
+ """
+ data_shape = self.__data.shape if self.__data is not None else ()
+
+ # Check selection
+ if len(selection) != len(data_shape):
+ raise ValueError(
+ "Selection length (%d) and data ndim (%d) mismatch" %
+ (len(selection), len(data_shape)))
+
+ # Check selection type
+ selectedDataNDim = 0
+ for element, size in zip(selection, data_shape):
+ if isinstance(element, int):
+ if not 0 <= element < size:
+ raise ValueError(
+ "Selected index (%d) outside data dimension range [0-%d]" %
+ (element, size))
+ elif element is None or element == slice(None):
+ selectedDataNDim += 1
+ else:
+ raise ValueError("Unsupported element in selection: %s" % element)
+
+ ndim = len(self.__axisNames)
+ if selectedDataNDim != ndim:
+ raise ValueError(
+ "Selection dimensions (%d) and number of axes (%d) mismatch" %
+ (selectedDataNDim, ndim))
+
+ # check permutation
+ if permutation is None:
+ permutation = tuple(range(ndim))
+
+ if set(permutation) != set(range(ndim)):
+ raise ValueError(
+ "Error in provided permutation: "
+ "Wrong size, elements out of range or duplicates")
+
+ inversePermutation = numpy.argsort(permutation)
+
+ axisNameChanged = False
+ customValueChanged = []
+ with blockSignals(*self.__axis):
+ index = 0
+ for element, axis in zip(selection, self.__axis):
+ if isinstance(element, int):
+ name = ""
+ else:
+ name = self.__axisNames[inversePermutation[index]]
+ index += 1
+
+ if axis.axisName() != name:
+ axis.setAxisName(name)
+ axisNameChanged = True
+
+ for element, axis in zip(selection, self.__axis):
+ value = element if isinstance(element, int) else 0
+ if axis.value() != value:
+ axis.setValue(value)
+
+ name = axis.axisName()
+ if name in self.__customAxisNames:
+ customValueChanged.append((name, value))
+
+ # Send signals that where disabled
+ if axisNameChanged:
+ self.selectedAxisChanged.emit()
+ for name, value in customValueChanged:
+ self.customAxisChanged.emit(name, value)
+ self.__updateSelectedData()
def setNamedAxesSelectorVisibility(self, visible):
"""Show or hide the combo-boxes allowing to map the plot axes
diff --git a/silx/gui/data/test/test_numpyaxesselector.py b/silx/gui/data/test/test_numpyaxesselector.py
index df11c1a..d37cff7 100644
--- a/silx/gui/data/test/test_numpyaxesselector.py
+++ b/silx/gui/data/test/test_numpyaxesselector.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2016-2017 European Synchrotron Radiation Facility
+# Copyright (c) 2016-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -76,7 +76,7 @@ class TestNumpyAxesSelector(TestCaseQt):
widget.setAxisNames(["x", "y", "z", "boum"])
widget.setData(data[0])
result = widget.selectedData()
- self.assertEqual(result, None)
+ self.assertIsNone(result)
widget.setData(data)
result = widget.selectedData()
self.assertTrue(numpy.array_equal(result, expectedResult))
diff --git a/silx/gui/dialog/ColormapDialog.py b/silx/gui/dialog/ColormapDialog.py
index 9c956f8..ed15947 100644
--- a/silx/gui/dialog/ColormapDialog.py
+++ b/silx/gui/dialog/ColormapDialog.py
@@ -79,6 +79,7 @@ from silx.gui.widgets.FloatEdit import FloatEdit
import weakref
from silx.math.combo import min_max
from silx.gui import icons
+from silx.gui.widgets.ColormapNameComboBox import ColormapNameComboBox
from silx.math.histogram import Histogramnd
_logger = logging.getLogger(__name__)
@@ -150,128 +151,6 @@ class _BoundaryWidget(qt.QWidget):
self._updateDisplayedText()
-class _ColormapNameCombox(qt.QComboBox):
- def __init__(self, parent=None):
- qt.QComboBox.__init__(self, parent)
- self.__initItems()
-
- LUT_NAME = qt.Qt.UserRole + 1
- LUT_COLORS = qt.Qt.UserRole + 2
-
- def __initItems(self):
- for colormapName in preferredColormaps():
- index = self.count()
- self.addItem(str.title(colormapName))
- self.setItemIcon(index, self.getIconPreview(name=colormapName))
- self.setItemData(index, colormapName, role=self.LUT_NAME)
-
- def getIconPreview(self, name=None, colors=None):
- """Return an icon preview from a LUT name.
-
- This icons are cached into a global structure.
-
- :param str name: Name of the LUT
- :param numpy.ndarray colors: Colors identify the LUT
- :rtype: qt.QIcon
- """
- if name is not None:
- iconKey = name
- else:
- iconKey = tuple(colors)
- icon = _colormapIconPreview.get(iconKey, None)
- if icon is None:
- icon = self.createIconPreview(name, colors)
- _colormapIconPreview[iconKey] = icon
- return icon
-
- def createIconPreview(self, name=None, colors=None):
- """Create and return an icon preview from a LUT name.
-
- This icons are cached into a global structure.
-
- :param str name: Name of the LUT
- :param numpy.ndarray colors: Colors identify the LUT
- :rtype: qt.QIcon
- """
- colormap = Colormap(name)
- size = 32
- if name is not None:
- lut = colormap.getNColors(size)
- else:
- lut = colors
- if len(lut) > size:
- # Down sample
- step = int(len(lut) / size)
- lut = lut[::step]
- elif len(lut) < size:
- # Over sample
- indexes = numpy.arange(size) / float(size) * (len(lut) - 1)
- indexes = indexes.astype("int")
- lut = lut[indexes]
- if lut is None or len(lut) == 0:
- return qt.QIcon()
-
- pixmap = qt.QPixmap(size, size)
- painter = qt.QPainter(pixmap)
- for i in range(size):
- rgb = lut[i]
- r, g, b = rgb[0], rgb[1], rgb[2]
- painter.setPen(qt.QColor(r, g, b))
- painter.drawPoint(qt.QPoint(i, 0))
-
- painter.drawPixmap(0, 1, size, size - 1, pixmap, 0, 0, size, 1)
- painter.end()
-
- return qt.QIcon(pixmap)
-
- def getCurrentName(self):
- return self.itemData(self.currentIndex(), self.LUT_NAME)
-
- def getCurrentColors(self):
- return self.itemData(self.currentIndex(), self.LUT_COLORS)
-
- def findLutName(self, name):
- return self.findData(name, role=self.LUT_NAME)
-
- def findLutColors(self, lut):
- for index in range(self.count()):
- if self.itemData(index, role=self.LUT_NAME) is not None:
- continue
- colors = self.itemData(index, role=self.LUT_COLORS)
- if colors is None:
- continue
- if numpy.array_equal(colors, lut):
- return index
- return -1
-
- def setCurrentLut(self, colormap):
- name = colormap.getName()
- if name is not None:
- self._setCurrentName(name)
- else:
- lut = colormap.getColormapLUT()
- self._setCurrentLut(lut)
-
- def _setCurrentLut(self, lut):
- index = self.findLutColors(lut)
- if index == -1:
- index = self.count()
- self.addItem("Custom")
- self.setItemIcon(index, self.getIconPreview(colors=lut))
- self.setItemData(index, None, role=self.LUT_NAME)
- self.setItemData(index, lut, role=self.LUT_COLORS)
- self.setCurrentIndex(index)
-
- def _setCurrentName(self, name):
- index = self.findLutName(name)
- if index < 0:
- index = self.count()
- self.addItem(str.title(name))
- self.setItemIcon(index, self.getIconPreview(name=name))
- self.setItemData(index, name, role=self.LUT_NAME)
- self.setCurrentIndex(index)
-
-
@enum.unique
class _DataInPlotMode(enum.Enum):
"""Enum for each mode of display of the data in the plot."""
@@ -329,7 +208,7 @@ class ColormapDialog(qt.QDialog):
formLayout.setSpacing(0)
# Colormap row
- self._comboBoxColormap = _ColormapNameCombox(parent=formWidget)
+ self._comboBoxColormap = ColormapNameComboBox(parent=formWidget)
self._comboBoxColormap.currentIndexChanged[int].connect(self._updateLut)
formLayout.addRow('Colormap:', self._comboBoxColormap)
diff --git a/silx/gui/hdf5/Hdf5Item.py b/silx/gui/hdf5/Hdf5Item.py
index 6ea870f..11a08b6 100644..100755
--- a/silx/gui/hdf5/Hdf5Item.py
+++ b/silx/gui/hdf5/Hdf5Item.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2016-2018 European Synchrotron Radiation Facility
+# Copyright (c) 2016-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -30,6 +30,7 @@ __date__ = "17/01/2019"
import logging
import collections
+import enum
from .. import qt
from .. import icons
@@ -44,6 +45,17 @@ _hdf5Formatter = Hdf5Formatter(textFormatter=_formatter)
# FIXME: The formatter should be an attribute of the Hdf5Model
+class DescriptionType(enum.Enum):
+ """List of available kind of description.
+ """
+ ERROR = "error"
+ DESCRIPTION = "description"
+ TITLE = "title"
+ PROGRAM = "program"
+ NAME = "name"
+ VALUE = "value"
+
+
class Hdf5Item(Hdf5Node):
"""Subclass of :class:`qt.QStandardItem` to represent an HDF5-like
item (dataset, file, group or link) as an element of a HDF5-like
@@ -62,6 +74,7 @@ class Hdf5Item(Hdf5Node):
self.__error = None
self.__text = text
self.__linkClass = linkClass
+ self.__description = None
self.__nx_class = None
Hdf5Node.__init__(self, parent, populateAll=populateAll)
@@ -367,7 +380,20 @@ class Hdf5Item(Hdf5Node):
text = ""
else:
text = self._getFormatter().textFormatter().toString(obj)
- self.__nx_class = text.strip('"')
+ text = text.strip('"')
+ # Check NX_class formatting
+ lower = text.lower()
+ if lower.startswith('nx'):
+ formatedNX_class = 'NX' + lower[2:]
+ if lower == 'nxcansas':
+ formatedNX_class = 'NXcanSAS' # That's the only class with capital letters...
+ if text != formatedNX_class:
+ _logger.error("NX_class: %s is malformed (should be %s)",
+ text,
+ formatedNX_class)
+ text = formatedNX_class
+
+ self.__nx_class = text
return self.__nx_class
def dataName(self, role):
@@ -430,31 +456,131 @@ class Hdf5Item(Hdf5Node):
return self._getFormatter().humanReadableValue(self.obj)
return None
+ _NEXUS_CLASS_TO_VALUE_CHILDREN = {
+ 'NXaperture': (
+ (DescriptionType.DESCRIPTION, 'description'),
+ ),
+ 'NXbeam_stop': (
+ (DescriptionType.DESCRIPTION, 'description'),
+ ),
+ 'NXdetector': (
+ (DescriptionType.NAME, 'local_name'),
+ (DescriptionType.DESCRIPTION, 'description')
+ ),
+ 'NXentry': (
+ (DescriptionType.TITLE, 'title'),
+ ),
+ 'NXenvironment': (
+ (DescriptionType.NAME, 'short_name'),
+ (DescriptionType.NAME, 'name'),
+ (DescriptionType.DESCRIPTION, 'description')
+ ),
+ 'NXinstrument': (
+ (DescriptionType.NAME, 'name'),
+ ),
+ 'NXlog': (
+ (DescriptionType.DESCRIPTION, 'description'),
+ ),
+ 'NXmirror': (
+ (DescriptionType.DESCRIPTION, 'description'),
+ ),
+ 'NXpositioner': (
+ (DescriptionType.NAME, 'name'),
+ ),
+ 'NXprocess': (
+ (DescriptionType.PROGRAM, 'program'),
+ ),
+ 'NXsample': (
+ (DescriptionType.TITLE, 'short_title'),
+ (DescriptionType.NAME, 'name'),
+ (DescriptionType.DESCRIPTION, 'description')
+ ),
+ 'NXsample_component': (
+ (DescriptionType.NAME, 'name'),
+ (DescriptionType.DESCRIPTION, 'description')
+ ),
+ 'NXsensor': (
+ (DescriptionType.NAME, 'short_name'),
+ (DescriptionType.NAME, 'name')
+ ),
+ 'NXsource': (
+ (DescriptionType.NAME, 'name'),
+ ), # or its 'short_name' attribute... This is not supported
+ 'NXsubentry': (
+ (DescriptionType.DESCRIPTION, 'definition'),
+ (DescriptionType.PROGRAM, 'program_name'),
+ (DescriptionType.TITLE, 'title'),
+ ),
+ }
+ """Mapping from NeXus class to child names containing data to use as value"""
+
+ def __computeDataDescription(self):
+ """Compute the data description of this item
+
+ :rtype: Tuple[kind, str]
+ """
+ if self.__isBroken or self.__error is not None:
+ self.obj # lazy loading of the object
+ return DescriptionType.ERROR, self.__error
+
+ if self.h5Class == silx.io.utils.H5Type.DATASET:
+ return DescriptionType.VALUE, self._getFormatter().humanReadableValue(self.obj)
+
+ elif self.isGroupObj() and self.nexusClassName:
+ # For NeXus groups, try to find a title or name
+ # By default, look for a title (most application definitions should have one)
+ defaultSequence = ((DescriptionType.TITLE, 'title'),)
+ sequence = self._NEXUS_CLASS_TO_VALUE_CHILDREN.get(self.nexusClassName, defaultSequence)
+ for kind, child_name in sequence:
+ for index in range(self.childCount()):
+ child = self.child(index)
+ if (isinstance(child, Hdf5Item) and
+ child.h5Class == silx.io.utils.H5Type.DATASET and
+ child.basename == child_name):
+ return kind, self._getFormatter().humanReadableValue(child.obj)
+
+ description = self.obj.attrs.get("desc", None)
+ if description is not None:
+ return DescriptionType.DESCRIPTION, description
+ else:
+ return None, None
+
+ def __getDataDescription(self):
+ """Returns a cached version of the data description
+
+ As the data description have to reach inside the HDF5 tree, the result
+ is cached. A better implementation could be to use a MRU cache, to avoid
+ to allocate too much data.
+
+ :rtype: Tuple[kind, str]
+ """
+ if self.__description is None:
+ self.__description = self.__computeDataDescription()
+ return self.__description
+
def dataDescription(self, role):
"""Data for the description column"""
if role == qt.Qt.DecorationRole:
+ kind, _label = self.__getDataDescription()
+ if kind is not None:
+ icon = icons.getQIcon("description-%s" % kind.value)
+ return icon
return None
if role == qt.Qt.TextAlignmentRole:
return qt.Qt.AlignTop | qt.Qt.AlignLeft
if role == qt.Qt.DisplayRole:
- if self.__isBroken:
- self.obj # lazy loading of the object
- return self.__error
- if "desc" in self.obj.attrs:
- text = self.obj.attrs["desc"]
- else:
- return ""
- return text
+ _kind, label = self.__getDataDescription()
+ return label
if role == qt.Qt.ToolTipRole:
if self.__error is not None:
self.obj # lazy loading of the object
self.__initH5Object()
return self.__error
- if "desc" in self.obj.attrs:
- text = self.obj.attrs["desc"]
+ kind, label = self.__getDataDescription()
+ if label is not None:
+ return "<b>%s</b><br/>%s" % (kind.value.capitalize(), label)
else:
return ""
- return "Description: %s" % text
return None
def dataNode(self, role):
diff --git a/silx/gui/hdf5/test/test_hdf5.py b/silx/gui/hdf5/test/test_hdf5.py
index 0ab4dc4..4bb43ff 100644..100755
--- a/silx/gui/hdf5/test/test_hdf5.py
+++ b/silx/gui/hdf5/test/test_hdf5.py
@@ -313,7 +313,7 @@ class TestHdf5TreeModel(TestCaseQt):
self.assertEqual(displayed[hdf5.Hdf5TreeModel.TYPE_COLUMN, qt.Qt.DisplayRole], "")
self.assertEqual(displayed[hdf5.Hdf5TreeModel.SHAPE_COLUMN, qt.Qt.DisplayRole], "")
self.assertEqual(displayed[hdf5.Hdf5TreeModel.VALUE_COLUMN, qt.Qt.DisplayRole], "")
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.DESCRIPTION_COLUMN, qt.Qt.DisplayRole], "")
+ self.assertEqual(displayed[hdf5.Hdf5TreeModel.DESCRIPTION_COLUMN, qt.Qt.DisplayRole], None)
self.assertEqual(displayed[hdf5.Hdf5TreeModel.NODE_COLUMN, qt.Qt.DisplayRole], "File")
def testGroupData(self):
@@ -345,7 +345,7 @@ class TestHdf5TreeModel(TestCaseQt):
self.assertEqual(displayed[hdf5.Hdf5TreeModel.TYPE_COLUMN, qt.Qt.DisplayRole], value.dtype.name)
self.assertEqual(displayed[hdf5.Hdf5TreeModel.SHAPE_COLUMN, qt.Qt.DisplayRole], "3")
self.assertEqual(displayed[hdf5.Hdf5TreeModel.VALUE_COLUMN, qt.Qt.DisplayRole], "[1 2 3]")
- self.assertEqual(displayed[hdf5.Hdf5TreeModel.DESCRIPTION_COLUMN, qt.Qt.DisplayRole], "")
+ self.assertEqual(displayed[hdf5.Hdf5TreeModel.DESCRIPTION_COLUMN, qt.Qt.DisplayRole], "[1 2 3]")
self.assertEqual(displayed[hdf5.Hdf5TreeModel.NODE_COLUMN, qt.Qt.DisplayRole], "Dataset")
def testDropLastAsFirst(self):
diff --git a/silx/gui/plot/ColorBar.py b/silx/gui/plot/ColorBar.py
index 9798123..fd4fdf8 100644
--- a/silx/gui/plot/ColorBar.py
+++ b/silx/gui/plot/ColorBar.py
@@ -874,7 +874,7 @@ class _TickBar(qt.QWidget):
fm = qt.QFontMetrics(font)
width = 0
for tick in self.ticks:
- width = max(fm.width(form.format(tick)), width)
+ width = max(fm.boundingRect(form.format(tick)).width(), width)
# if the length of the string are too long we are moving to scientific
# display
diff --git a/silx/gui/plot/CurvesROIWidget.py b/silx/gui/plot/CurvesROIWidget.py
index 050b344..4508c60 100644
--- a/silx/gui/plot/CurvesROIWidget.py
+++ b/silx/gui/plot/CurvesROIWidget.py
@@ -42,11 +42,13 @@ import numpy
from silx.io import dictdump
from silx.utils import deprecation
from silx.utils.weakref import WeakMethodProxy
+from silx.utils.proxy import docstring
from .. import icons, qt
-from silx.gui.plot.items.curve import Curve
from silx.math.combo import min_max
import weakref
from silx.gui.widgets.TableWidget import TableWidget
+from . import items
+from .items.roi import _RegionOfInterestBase
_logger = logging.getLogger(__name__)
@@ -66,12 +68,15 @@ class CurvesROIWidget(qt.QWidget):
sigROIWidgetSignal = qt.Signal(object)
"""Signal of ROIs modifications.
- Modification information if given as a dict with an 'event' key
- providing the type of events.
- Type of events:
- - AddROI, DelROI, LoadROI and ResetROI with keys: 'roilist', 'roidict'
- - selectionChanged with keys: 'row', 'col' 'roi', 'key', 'colheader',
- 'rowheader'
+
+ Modification information if given as a dict with an 'event' key
+ providing the type of events.
+
+ Type of events:
+
+ - AddROI, DelROI, LoadROI and ResetROI with keys: 'roilist', 'roidict'
+ - selectionChanged with keys: 'row', 'col' 'roi', 'key', 'colheader',
+ 'rowheader'
"""
sigROISignal = qt.Signal(object)
@@ -1045,12 +1050,12 @@ class ROITable(TableWidget):
_indexNextROI = 0
-class ROI(qt.QObject):
+class ROI(_RegionOfInterestBase):
"""The Region Of Interest is defined by:
- A name
- A type. The type is the label of the x axis. This can be used to apply or
- not some ROI to a curve and do some post processing.
+ not some ROI to a curve and do some post processing.
- The x coordinate of the left limit (fromdata)
- The x coordinate of the right limit (todata)
@@ -1064,17 +1069,22 @@ class ROI(qt.QObject):
"""Signal emitted when the ROI is edited"""
def __init__(self, name, fromdata=None, todata=None, type_=None):
- qt.QObject.__init__(self)
- assert type(name) is str
+ _RegionOfInterestBase.__init__(self, name=name)
global _indexNextROI
self._id = _indexNextROI
_indexNextROI += 1
- self._name = name
self._fromdata = fromdata
self._todata = todata
self._type = type_ or 'Default'
+ self.sigItemChanged.connect(self.__itemChanged)
+
+ def __itemChanged(self, event):
+ """Handle name change"""
+ if event == items.ItemChangedType.NAME:
+ self.sigChanged.emit()
+
def getID(self):
"""
@@ -1098,23 +1108,6 @@ class ROI(qt.QObject):
"""
return self._type
- def setName(self, name):
- """
- Set the name of the :class:`ROI`
-
- :param str name:
- """
- if self._name != name:
- self._name = name
- self.sigChanged.emit()
-
- def getName(self):
- """
-
- :return str: name of the :class:`ROI`
- """
- return self._name
-
def setFrom(self, frm):
"""
@@ -1161,7 +1154,7 @@ class ROI(qt.QObject):
"""
ddict = {
'type': self._type,
- 'name': self._name,
+ 'name': self.getName(),
'from': self._fromdata,
'to': self._todata,
}
@@ -1191,7 +1184,7 @@ class ROI(qt.QObject):
:return: True if the ROI is the `ICR`
"""
- return self._name == 'ICR'
+ return self.getName() == 'ICR'
def computeRawAndNetCounts(self, curve):
"""Compute the Raw and net counts in the ROI for the given curve.
@@ -1208,7 +1201,7 @@ class ROI(qt.QObject):
:param CurveItem curve:
:return tuple: rawCount, netCount
"""
- assert isinstance(curve, Curve) or curve is None
+ assert isinstance(curve, items.Curve) or curve is None
if curve is None:
return None, None
@@ -1251,7 +1244,7 @@ class ROI(qt.QObject):
:param CurveItem curve:
:return tuple: rawArea, netArea
"""
- assert isinstance(curve, Curve) or curve is None
+ assert isinstance(curve, items.Curve) or curve is None
if curve is None:
return None, None
diff --git a/silx/gui/plot/LegendSelector.py b/silx/gui/plot/LegendSelector.py
index b9d0fd3..a9d89db 100644..100755
--- a/silx/gui/plot/LegendSelector.py
+++ b/silx/gui/plot/LegendSelector.py
@@ -38,63 +38,14 @@ import weakref
import numpy
from .. import qt, colors
+from ..widgets.LegendIconWidget import LegendIconWidget
from . import items
_logger = logging.getLogger(__name__)
-# Build all symbols
-# Courtesy of the pyqtgraph project
-Symbols = dict([(name, qt.QPainterPath())
- for name in ['o', 's', 't', 'd', '+', 'x', '.', ',']])
-Symbols['o'].addEllipse(qt.QRectF(.1, .1, .8, .8))
-Symbols['.'].addEllipse(qt.QRectF(.3, .3, .4, .4))
-Symbols[','].addEllipse(qt.QRectF(.4, .4, .2, .2))
-Symbols['s'].addRect(qt.QRectF(.1, .1, .8, .8))
-
-coords = {
- 't': [(0.5, 0.), (.1, .8), (.9, .8)],
- 'd': [(0.1, 0.5), (0.5, 0.), (0.9, 0.5), (0.5, 1.)],
- '+': [(0.0, 0.40), (0.40, 0.40), (0.40, 0.), (0.60, 0.),
- (0.60, 0.40), (1., 0.40), (1., 0.60), (0.60, 0.60),
- (0.60, 1.), (0.40, 1.), (0.40, 0.60), (0., 0.60)],
- 'x': [(0.0, 0.40), (0.40, 0.40), (0.40, 0.), (0.60, 0.),
- (0.60, 0.40), (1., 0.40), (1., 0.60), (0.60, 0.60),
- (0.60, 1.), (0.40, 1.), (0.40, 0.60), (0., 0.60)]
-}
-for s, c in coords.items():
- Symbols[s].moveTo(*c[0])
- for x, y in c[1:]:
- Symbols[s].lineTo(x, y)
- Symbols[s].closeSubpath()
-tr = qt.QTransform()
-tr.rotate(45)
-Symbols['x'].translate(qt.QPointF(-0.5, -0.5))
-Symbols['x'] = tr.map(Symbols['x'])
-Symbols['x'].translate(qt.QPointF(0.5, 0.5))
-
-NoSymbols = (None, 'None', 'none', '', ' ')
-"""List of values resulting in no symbol being displayed for a curve"""
-
-
-LineStyles = {
- None: qt.Qt.NoPen,
- 'None': qt.Qt.NoPen,
- 'none': qt.Qt.NoPen,
- '': qt.Qt.NoPen,
- ' ': qt.Qt.NoPen,
- '-': qt.Qt.SolidLine,
- '--': qt.Qt.DashLine,
- ':': qt.Qt.DotLine,
- '-.': qt.Qt.DashDotLine
-}
-"""Conversion from matplotlib-like linestyle to Qt"""
-
-NoLineStyle = (None, 'None', 'none', '', ' ')
-"""List of style values resulting in no line being displayed for a curve"""
-
-
-class LegendIcon(qt.QWidget):
+
+class LegendIcon(LegendIconWidget):
"""Object displaying a curve linestyle and symbol.
:param QWidget parent: See :class:`QWidget`
@@ -105,35 +56,8 @@ class LegendIcon(qt.QWidget):
def __init__(self, parent=None, curve=None):
super(LegendIcon, self).__init__(parent)
self._curveRef = None
-
- # Visibilities
- self.showLine = True
- self.showSymbol = True
-
- # Line attributes
- self.lineStyle = qt.Qt.NoPen
- self.lineWidth = 1.
- self.lineColor = qt.Qt.green
-
- self.symbol = ''
- # Symbol attributes
- self.symbolStyle = qt.Qt.SolidPattern
- self.symbolColor = qt.Qt.green
- self.symbolOutlineBrush = qt.QBrush(qt.Qt.white)
-
- # Control widget size: sizeHint "is the only acceptable
- # alternative, so the widget can never grow or shrink"
- # (c.f. Qt Doc, enum QSizePolicy::Policy)
- self.setSizePolicy(qt.QSizePolicy.Fixed,
- qt.QSizePolicy.Fixed)
-
self.setCurve(curve)
- def sizeHint(self):
- return qt.QSize(50, 15)
-
- # Synchronize with a curve
-
def getCurve(self):
"""Returns curve associated to this widget
@@ -206,125 +130,6 @@ class LegendIcon(qt.QWidget):
items.ItemChangedType.HIGHLIGHTED_STYLE):
self._update()
- # Modify Symbol
- def setSymbol(self, symbol):
- symbol = str(symbol)
- if symbol not in NoSymbols:
- if symbol not in Symbols:
- raise ValueError("Unknown symbol: <%s>" % symbol)
- self.symbol = symbol
- # self.update() after set...?
- # Does not seem necessary
-
- def setSymbolColor(self, color):
- """
- :param color: determines the symbol color
- :type style: qt.QColor
- """
- self.symbolColor = qt.QColor(color)
-
- # Modify Line
-
- def setLineColor(self, color):
- self.lineColor = qt.QColor(color)
-
- def setLineWidth(self, width):
- self.lineWidth = float(width)
-
- def setLineStyle(self, style):
- """Set the linestyle.
-
- Possible line styles:
-
- - '', ' ', 'None': No line
- - '-': solid
- - '--': dashed
- - ':': dotted
- - '-.': dash and dot
-
- :param str style: The linestyle to use
- """
- if style not in LineStyles:
- raise ValueError('Unknown style: %s', style)
- self.lineStyle = LineStyles[style]
-
- # Paint
-
- def paintEvent(self, event):
- """
- :param event: event
- :type event: QPaintEvent
- """
- painter = qt.QPainter(self)
- self.paint(painter, event.rect(), self.palette())
-
- def paint(self, painter, rect, palette):
- painter.save()
- painter.setRenderHint(qt.QPainter.Antialiasing)
- # Scale painter to the icon height
- # current -> width = 2.5, height = 1.0
- scale = float(self.height())
- ratio = float(self.width()) / scale
- painter.scale(scale,
- scale)
- symbolOffset = qt.QPointF(.5 * (ratio - 1.), 0.)
- # Determine and scale offset
- offset = qt.QPointF(float(rect.left()) / scale, float(rect.top()) / scale)
-
- # Override color when disabled
- if self.isEnabled():
- overrideColor = None
- else:
- overrideColor = palette.color(qt.QPalette.Disabled,
- qt.QPalette.WindowText)
-
- # Draw BG rectangle (for debugging)
- # bottomRight = qt.QPointF(
- # float(rect.right())/scale,
- # float(rect.bottom())/scale)
- # painter.fillRect(qt.QRectF(offset, bottomRight),
- # qt.QBrush(qt.Qt.green))
- llist = []
- if self.showLine:
- linePath = qt.QPainterPath()
- linePath.moveTo(0., 0.5)
- linePath.lineTo(ratio, 0.5)
- # linePath.lineTo(2.5, 0.5)
- lineBrush = qt.QBrush(
- self.lineColor if overrideColor is None else overrideColor)
- linePen = qt.QPen(
- lineBrush,
- (self.lineWidth / self.height()),
- self.lineStyle,
- qt.Qt.FlatCap
- )
- llist.append((linePath, linePen, lineBrush))
- if (self.showSymbol and len(self.symbol) and
- self.symbol not in NoSymbols):
- # PITFALL ahead: Let this be a warning to others
- # symbolPath = Symbols[self.symbol]
- # Copy before translate! Dict is a mutable type
- symbolPath = qt.QPainterPath(Symbols[self.symbol])
- symbolPath.translate(symbolOffset)
- symbolBrush = qt.QBrush(
- self.symbolColor if overrideColor is None else overrideColor,
- self.symbolStyle)
- symbolPen = qt.QPen(
- self.symbolOutlineBrush, # Brush
- 1. / self.height(), # Width
- qt.Qt.SolidLine # Style
- )
- llist.append((symbolPath,
- symbolPen,
- symbolBrush))
- # Draw
- for path, pen, brush in llist:
- path.translate(offset)
- painter.setPen(pen)
- painter.setBrush(brush)
- painter.drawPath(path)
- painter.restore()
-
class LegendModel(qt.QAbstractListModel):
"""Data model of curve legends.
@@ -476,7 +281,7 @@ class LegendModel(qt.QAbstractListModel):
new = []
for (legend, icon) in llist:
linestyle = icon.get('linestyle', None)
- if linestyle in NoLineStyle:
+ if LegendIconWidget.isEmptyLineStyle(linestyle):
# Curve had no line, give it one and hide it
# So when toggle line, it will display a solid line
showLine = False
@@ -485,7 +290,7 @@ class LegendModel(qt.QAbstractListModel):
showLine = True
symbol = icon.get('symbol', None)
- if symbol in NoSymbols:
+ if LegendIconWidget.isEmptySymbol(symbol):
# Curve had no symbol, give it one and hide it
# So when toggle symbol, it will display 'o'
showSymbol = False
@@ -959,7 +764,7 @@ class LegendListContextMenu(qt.QMenu):
}
flag = modelIndex.data(LegendModel.showSymbolRole)
symbol = modelIndex.data(LegendModel.iconSymbolRole)
- visible = not flag or symbol in NoSymbols
+ visible = not flag or LegendIconWidget.isEmptySymbol(symbol)
_logger.debug(
'togglePointsAction -- Symbols visible: %s', str(visible))
diff --git a/silx/gui/plot/PlotInteraction.py b/silx/gui/plot/PlotInteraction.py
index 27abd10..abfcf79 100644
--- a/silx/gui/plot/PlotInteraction.py
+++ b/silx/gui/plot/PlotInteraction.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2014-2018 European Synchrotron Radiation Facility
+# Copyright (c) 2014-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -1079,7 +1079,10 @@ class ItemsInteraction(ClickOrDrag, _PlotInteraction):
applyZoomToPlot(self.machine.plot, scaleF, (x, y))
def onMove(self, x, y):
- marker = self.machine.plot._pickMarker(x, y)
+ result = self.machine.plot._pickTopMost(
+ x, y, lambda item: isinstance(item, items.MarkerBase))
+ marker = result.getItem() if result is not None else None
+
if marker is not None:
dataPos = self.machine.plot.pixelToData(x, y)
assert dataPos is not None
@@ -1151,10 +1154,14 @@ class ItemsInteraction(ClickOrDrag, _PlotInteraction):
"""
if btn == LEFT_BTN:
- marker = self.plot._pickMarker(
- x, y, lambda m: m.isSelectable())
- if marker is not None:
- xData, yData = marker.getPosition()
+ result = self.plot._pickTopMost(x, y, lambda i: i.isSelectable())
+ if result is None:
+ return None
+
+ item = result.getItem()
+
+ if isinstance(item, items.MarkerBase):
+ xData, yData = item.getPosition()
if xData is None:
xData = [0, 1]
if yData is None:
@@ -1162,58 +1169,44 @@ class ItemsInteraction(ClickOrDrag, _PlotInteraction):
eventDict = prepareMarkerSignal('markerClicked',
'left',
- marker.getLegend(),
+ item.getLegend(),
'marker',
- marker.isDraggable(),
- marker.isSelectable(),
+ item.isDraggable(),
+ item.isSelectable(),
(xData, yData),
(x, y), None)
return eventDict
- else:
- picked = self.plot._pickImageOrCurve(
- x, y, lambda item: item.isSelectable())
-
- if picked is None:
- pass
-
- elif picked[0] == 'curve':
- curve = picked[1]
- indices = picked[2]
-
- dataPos = self.plot.pixelToData(x, y)
- assert dataPos is not None
-
- xData = curve.getXData(copy=False)
- yData = curve.getYData(copy=False)
-
- eventDict = prepareCurveSignal('left',
- curve.getLegend(),
- 'curve',
- xData[indices],
- yData[indices],
- dataPos[0], dataPos[1],
- x, y)
- return eventDict
-
- elif picked[0] == 'image':
- image = picked[1]
-
- dataPos = self.plot.pixelToData(x, y)
- assert dataPos is not None
-
- # Get corresponding coordinate in image
- origin = image.getOrigin()
- scale = image.getScale()
- column = int((dataPos[0] - origin[0]) / float(scale[0]))
- row = int((dataPos[1] - origin[1]) / float(scale[1]))
- eventDict = prepareImageSignal('left',
- image.getLegend(),
- 'image',
- column, row,
- dataPos[0], dataPos[1],
- x, y)
- return eventDict
+ elif isinstance(item, items.Curve):
+ dataPos = self.plot.pixelToData(x, y)
+ assert dataPos is not None
+
+ xData = item.getXData(copy=False)
+ yData = item.getYData(copy=False)
+
+ indices = result.getIndices(copy=False)
+ eventDict = prepareCurveSignal('left',
+ item.getLegend(),
+ 'curve',
+ xData[indices],
+ yData[indices],
+ dataPos[0], dataPos[1],
+ x, y)
+ return eventDict
+
+ elif isinstance(item, items.ImageBase):
+ dataPos = self.plot.pixelToData(x, y)
+ assert dataPos is not None
+
+ indices = result.getIndices(copy=False)
+ row, column = indices[0][0], indices[1][0]
+ eventDict = prepareImageSignal('left',
+ item.getLegend(),
+ 'image',
+ column, row,
+ dataPos[0], dataPos[1],
+ x, y)
+ return eventDict
return None
@@ -1240,6 +1233,15 @@ class ItemsInteraction(ClickOrDrag, _PlotInteraction):
posDataCursor)
self.plot.notify(**eventDict)
+ @staticmethod
+ def __isDraggableItem(item):
+ return isinstance(item, items.DraggableMixIn) and item.isDraggable()
+
+ def __terminateDrag(self):
+ """Finalize a drag operation by reseting to initial state"""
+ self.plot.setGraphCursorShape()
+ self.draggedItemRef = None
+
def beginDrag(self, x, y):
"""Handle begining of drag interaction
@@ -1250,78 +1252,56 @@ class ItemsInteraction(ClickOrDrag, _PlotInteraction):
self._lastPos = self.plot.pixelToData(x, y)
assert self._lastPos is not None
- self.imageLegend = None
- self.markerLegend = None
- marker = self.plot._pickMarker(
- x, y, lambda m: m.isDraggable())
+ result = self.plot._pickTopMost(x, y, self.__isDraggableItem)
+ item = result.getItem() if result is not None else None
+
+ self.draggedItemRef = None if item is None else weakref.ref(item)
+
+ if item is None:
+ self.__terminateDrag()
+ return False
+
+ if isinstance(item, items.MarkerBase):
+ self._signalMarkerMovingEvent('markerMoving', item, x, y)
- if marker is not None:
- self.markerLegend = marker.getLegend()
- self._signalMarkerMovingEvent('markerMoving', marker, x, y)
- else:
- picked = self.plot._pickImageOrCurve(
- x,
- y,
- lambda item:
- hasattr(item, 'isDraggable') and item.isDraggable())
- if picked is None:
- self.imageLegend = None
- self.plot.setGraphCursorShape()
- return False
- else:
- assert picked[0] == 'image' # For now only drag images
- self.imageLegend = picked[1].getLegend()
return True
def drag(self, x, y):
dataPos = self.plot.pixelToData(x, y)
assert dataPos is not None
- xData, yData = dataPos
- if self.markerLegend is not None:
- marker = self.plot._getMarker(self.markerLegend)
- if marker is not None:
- marker.setPosition(xData, yData)
+ item = None if self.draggedItemRef is None else self.draggedItemRef()
+ if item is not None:
+ item.drag(self._lastPos, dataPos)
- self._signalMarkerMovingEvent(
- 'markerMoving', marker, x, y)
+ if isinstance(item, items.MarkerBase):
+ self._signalMarkerMovingEvent('markerMoving', item, x, y)
- if self.imageLegend is not None:
- image = self.plot.getImage(self.imageLegend)
- origin = image.getOrigin()
- xImage = origin[0] + xData - self._lastPos[0]
- yImage = origin[1] + yData - self._lastPos[1]
- image.setOrigin((xImage, yImage))
-
- self._lastPos = xData, yData
+ self._lastPos = dataPos
def endDrag(self, startPos, endPos):
- if self.markerLegend is not None:
- marker = self.plot._getMarker(self.markerLegend)
- posData = list(marker.getPosition())
+ item = None if self.draggedItemRef is None else self.draggedItemRef()
+ if item is not None and isinstance(item, items.MarkerBase):
+ posData = list(item.getPosition())
if posData[0] is None:
- posData[0] = [0, 1]
+ posData[0] = 1.
if posData[1] is None:
- posData[1] = [0, 1]
+ posData[1] = 1.
eventDict = prepareMarkerSignal(
'markerMoved',
'left',
- marker.getLegend(),
+ item.getLegend(),
'marker',
- marker.isDraggable(),
- marker.isSelectable(),
+ item.isDraggable(),
+ item.isSelectable(),
posData)
self.plot.notify(**eventDict)
- self.plot.setGraphCursorShape()
-
- del self.markerLegend
- del self.imageLegend
- del self._lastPos
+ self.__terminateDrag()
def cancel(self):
- self.plot.setGraphCursorShape()
+ self.__terminateDrag()
class ItemsInteractionForCombo(ItemsInteraction):
@@ -1329,22 +1309,16 @@ class ItemsInteractionForCombo(ItemsInteraction):
"""
class Idle(ItemsInteraction.Idle):
+ @staticmethod
+ def __isItemSelectableOrDraggable(item):
+ return (item.isSelectable() or (
+ isinstance(item, items.DraggableMixIn) and item.isDraggable()))
+
def onPress(self, x, y, btn):
if btn == LEFT_BTN:
- def test(item):
- return (item.isSelectable() or
- (isinstance(item, items.DraggableMixIn) and
- item.isDraggable()))
-
- picked = self.machine.plot._pickMarker(x, y, test)
- if picked is not None:
- itemInteraction = True
-
- else:
- picked = self.machine.plot._pickImageOrCurve(x, y, test)
- itemInteraction = picked is not None
-
- if itemInteraction: # Request focus and handle interaction
+ result = self.machine.plot._pickTopMost(
+ x, y, self.__isItemSelectableOrDraggable)
+ if result is not None: # Request focus and handle interaction
self.goto('clickOrDrag', x, y)
return True
else: # Do not request focus
@@ -1658,7 +1632,7 @@ class PlotInteraction(object):
plot = self._plot()
assert plot is not None
- if color not in (None, 'video inverted'):
+ if isinstance(color, numpy.ndarray) or color not in (None, 'video inverted'):
color = colors.rgba(color)
if mode in ('draw', 'select-draw'):
diff --git a/silx/gui/plot/PlotWidget.py b/silx/gui/plot/PlotWidget.py
index 9b9b4d2..49e444a 100644..100755
--- a/silx/gui/plot/PlotWidget.py
+++ b/silx/gui/plot/PlotWidget.py
@@ -39,10 +39,6 @@ _logger = logging.getLogger(__name__)
from collections import OrderedDict, namedtuple
-try:
- from collections import abc
-except ImportError: # Python2 support
- import collections as abc
from contextlib import contextmanager
import datetime as dt
import itertools
@@ -60,6 +56,7 @@ try:
except ImportError:
_logger.debug("matplotlib not available")
+import six
from ..colors import Colormap
from .. import colors
from . import PlotInteraction
@@ -311,7 +308,7 @@ class PlotWidget(qt.QMainWindow):
if callable(backend):
return backend
- elif isinstance(backend, str):
+ elif isinstance(backend, six.string_types):
backend = backend.lower()
if backend in ('matplotlib', 'mpl'):
try:
@@ -337,7 +334,7 @@ class PlotWidget(qt.QMainWindow):
return backendClass
- elif isinstance(backend, abc.Iterable):
+ elif isinstance(backend, (tuple, list)):
for b in backend:
try:
return self.__getBackendClass(b)
@@ -606,7 +603,7 @@ class PlotWidget(qt.QMainWindow):
elif isinstance(item, (items.Marker,
items.XMarker, items.YMarker)):
kind = 'marker'
- elif isinstance(item, items.Shape):
+ elif isinstance(item, (items.Shape, items.BoundingRect)):
kind = 'item'
elif isinstance(item, items.Histogram):
kind = 'histogram'
@@ -710,7 +707,8 @@ class PlotWidget(qt.QMainWindow):
xlabel=None, ylabel=None, yaxis=None,
xerror=None, yerror=None, z=None, selectable=None,
fill=None, resetzoom=True,
- histogram=None, copy=True):
+ histogram=None, copy=True,
+ baseline=None):
"""Add a 1D curve given by x an y to the graph.
Curves are uniquely identified by their legend.
@@ -791,6 +789,8 @@ class PlotWidget(qt.QMainWindow):
- 'center'
:param bool copy: True make a copy of the data (default),
False to use provided arrays.
+ :param baseline: curve baseline
+ :type: Union[None,float,numpy.ndarray]
:returns: The key string identify this curve
"""
# This is an histogram, use addHistogram
@@ -845,6 +845,7 @@ class PlotWidget(qt.QMainWindow):
curve.setColor(default_color)
curve.setLineStyle(default_linestyle)
curve.setSymbol(self._defaultPlotPoints)
+ curve._setBaseline(baseline=baseline)
# Do not emit sigActiveCurveChanged,
# it will be sent once with _setActiveItem
@@ -887,7 +888,7 @@ class PlotWidget(qt.QMainWindow):
if len(x) > 0 and isinstance(x[0], dt.datetime):
x = [timestamp(d) for d in x]
- curve.setData(x, y, xerror, yerror, copy=copy)
+ curve.setData(x, y, xerror, yerror, baseline=baseline, copy=copy)
if replace: # Then remove all other curves
for c in self.getAllCurves(withhidden=True):
@@ -924,7 +925,9 @@ class PlotWidget(qt.QMainWindow):
fill=None,
align='center',
resetzoom=True,
- copy=True):
+ copy=True,
+ z=None,
+ baseline=None):
"""Add an histogram to the graph.
This is NOT computing the histogram, this method takes as parameter
@@ -955,6 +958,9 @@ class PlotWidget(qt.QMainWindow):
:param bool resetzoom: True (the default) to reset the zoom.
:param bool copy: True make a copy of the data (default),
False to use provided arrays.
+ :param int z: Layer on which to draw the histogram
+ :param baseline: histogram baseline
+ :type: Union[None,float,numpy.ndarray]
:returns: The key string identify this histogram
"""
legend = 'Unnamed histogram' if legend is None else str(legend)
@@ -974,9 +980,12 @@ class PlotWidget(qt.QMainWindow):
histo.setColor(color)
if fill is not None:
histo.setFill(fill)
+ if z is not None:
+ histo.setZValue(z=z)
# Set histogram data
- histo.setData(histogram, edges, align=align, copy=copy)
+ histo.setData(histogram=histogram, edges=edges, baseline=baseline,
+ align=align, copy=copy)
if mustBeAdded:
self._add(histo)
@@ -1307,7 +1316,8 @@ class PlotWidget(qt.QMainWindow):
color=None,
selectable=False,
draggable=False,
- constraint=None):
+ constraint=None,
+ yaxis='left'):
"""Add a vertical line marker to the plot.
Markers are uniquely identified by their legend.
@@ -1333,12 +1343,14 @@ class PlotWidget(qt.QMainWindow):
:type constraint: None or a callable that takes the coordinates of
the current cursor position in the plot as input
and that returns the filtered coordinates.
+ :param str yaxis: The Y axis this marker belongs to in: 'left', 'right'
:return: The key string identify this marker
"""
return self._addMarker(x=x, y=None, legend=legend,
text=text, color=color,
selectable=selectable, draggable=draggable,
- symbol=None, constraint=constraint)
+ symbol=None, constraint=constraint,
+ yaxis=yaxis)
def addYMarker(self, y,
legend=None,
@@ -1346,7 +1358,8 @@ class PlotWidget(qt.QMainWindow):
color=None,
selectable=False,
draggable=False,
- constraint=None):
+ constraint=None,
+ yaxis='left'):
"""Add a horizontal line marker to the plot.
Markers are uniquely identified by their legend.
@@ -1372,12 +1385,14 @@ class PlotWidget(qt.QMainWindow):
:type constraint: None or a callable that takes the coordinates of
the current cursor position in the plot as input
and that returns the filtered coordinates.
+ :param str yaxis: The Y axis this marker belongs to in: 'left', 'right'
:return: The key string identify this marker
"""
return self._addMarker(x=None, y=y, legend=legend,
text=text, color=color,
selectable=selectable, draggable=draggable,
- symbol=None, constraint=constraint)
+ symbol=None, constraint=constraint,
+ yaxis=yaxis)
def addMarker(self, x, y, legend=None,
text=None,
@@ -1385,7 +1400,8 @@ class PlotWidget(qt.QMainWindow):
selectable=False,
draggable=False,
symbol='+',
- constraint=None):
+ constraint=None,
+ yaxis='left'):
"""Add a point marker to the plot.
Markers are uniquely identified by their legend.
@@ -1423,6 +1439,7 @@ class PlotWidget(qt.QMainWindow):
:type constraint: None or a callable that takes the coordinates of
the current cursor position in the plot as input
and that returns the filtered coordinates.
+ :param str yaxis: The Y axis this marker belongs to in: 'left', 'right'
:return: The key string identify this marker
"""
if x is None:
@@ -1436,12 +1453,14 @@ class PlotWidget(qt.QMainWindow):
return self._addMarker(x=x, y=y, legend=legend,
text=text, color=color,
selectable=selectable, draggable=draggable,
- symbol=symbol, constraint=constraint)
+ symbol=symbol, constraint=constraint,
+ yaxis=yaxis)
def _addMarker(self, x, y, legend,
text, color,
selectable, draggable,
- symbol, constraint):
+ symbol, constraint,
+ yaxis=None):
"""Common method for adding point, vline and hline marker.
See :meth:`addMarker` for argument documentation.
@@ -1487,6 +1506,7 @@ class PlotWidget(qt.QMainWindow):
marker._setDraggable(draggable)
if symbol is not None:
marker.setSymbol(symbol)
+ marker.setYAxis(yaxis)
# TODO to improve, but this ensure constraint is applied
marker.setPosition(x, y)
@@ -2692,6 +2712,7 @@ class PlotWidget(qt.QMainWindow):
"""Redraw the plot immediately."""
for item in self._contentToUpdate:
item._update(self._backend)
+
self._contentToUpdate = []
self._backend.replot()
self._dirty = False # reset dirty flag
@@ -2862,7 +2883,18 @@ class PlotWidget(qt.QMainWindow):
:rtype: A tuple of 2 floats: (xData, yData) or None.
"""
assert axis in ("left", "right")
- return self._backend.pixelToData(x, y, axis=axis, check=check)
+
+ if x is None:
+ x = self.width() // 2
+ if y is None:
+ y = self.height() // 2
+
+ if check:
+ left, top, width, height = self.getPlotBoundsInPixels()
+ if not (left <= x <= left + width and top <= y <= top + height):
+ return None
+
+ return self._backend.pixelToData(x, y, axis)
def getPlotBoundsInPixels(self):
"""Plot area bounds in widget coordinates in pixels.
@@ -2880,29 +2912,6 @@ class PlotWidget(qt.QMainWindow):
"""
self._backend.setGraphCursorShape(cursor)
- def _pickMarker(self, x, y, test=None):
- """Pick a marker at the given position.
-
- To use for interaction implementation.
-
- :param float x: X position in pixels.
- :param float y: Y position in pixels.
- :param test: A callable to call for each picked marker to filter
- picked markers. If None (default), do not filter markers.
- """
- if test is None:
- def test(mark):
- return True
-
- markers = self._backend.pickItems(x, y, kinds=('marker',))
- legends = [m['legend'] for m in markers if m['kind'] == 'marker']
-
- for legend in reversed(legends):
- marker = self._getMarker(legend)
- if marker is not None and test(marker):
- return marker
- return None
-
def _getAllMarkers(self, just_legend=False):
"""Returns all markers' legend or objects
@@ -2924,73 +2933,41 @@ class PlotWidget(qt.QMainWindow):
"""
return self._getItem(kind='marker', legend=legend)
- def _pickImageOrCurve(self, x, y, test=None):
- """Pick an image or a curve at the given position.
+ def pickItems(self, x, y, condition=None):
+ """Generator of picked items in the plot at given position.
- To use for interaction implementation.
+ Items are returned from front to back.
:param float x: X position in pixels
:param float y: Y position in pixels
- :param test: A callable to call for each picked item to filter
- picked items. If None (default), do not filter items.
+ :param callable condition:
+ Callable taking an item as input and returning False for items
+ to skip during picking. If None (default) no item is skipped.
+ :return: Iterable of :class:`PickingResult` objects at picked position.
+ Items are ordered from front to back.
"""
- if test is None:
- def test(i):
- return True
-
- allItems = self._backend.pickItems(x, y, kinds=('curve', 'image'))
- allItems = [item for item in allItems
- if item['kind'] in ['curve', 'image']]
-
- for item in reversed(allItems):
- kind, legend = item['kind'], item['legend']
- if kind == 'curve':
- curve = self.getCurve(legend)
- if curve is not None and test(curve):
- return kind, curve, item['indices']
-
- elif kind == 'image':
- image = self.getImage(legend)
- if image is not None and test(image):
- return kind, image, None
+ for item in reversed(self._backend.getItemsFromBackToFront(condition=condition)):
+ result = item.pick(x, y)
+ if result is not None:
+ yield result
- else:
- _logger.warning('Unsupported kind: %s', kind)
-
- return None
+ def _pickTopMost(self, x, y, condition=None):
+ """Returns top-most picked item in the plot at given position.
- def _pick(self, x, y):
- """Pick items in the plot at given position.
+ Items are checked from front to back.
:param float x: X position in pixels
:param float y: Y position in pixels
- :return: Iterable of (plot item, indices) at picked position.
- Items are ordered from back to front.
- """
- items = []
-
- # Convert backend result to plot items
- for itemInfo in self._backend.pickItems(
- x, y, kinds=('marker', 'curve', 'image')):
- kind, legend = itemInfo['kind'], itemInfo['legend']
-
- if kind in ('marker', 'image'):
- item = self._getItem(kind=kind, legend=legend)
- indices = None # TODO compute indices for images
-
- else: # backend kind == 'curve'
- for kind in ('curve', 'histogram', 'scatter'):
- item = self._getItem(kind=kind, legend=legend)
- if item is not None:
- indices = itemInfo['indices']
- break
- else:
- _logger.error(
- 'Cannot find corresponding picked item')
- continue
- items.append((item, indices))
-
- return tuple(items)
+ :param callable condition:
+ Callable taking an item as input and returning False for items
+ to skip during picking. If None (default) no item is skipped.
+ :return: :class:`PickingResult` object at picked position.
+ If no item is picked, it returns None
+ :rtype: Union[None,PickingResult]
+ """
+ for result in self.pickItems(x, y, condition):
+ return result
+ return None
# User event handling #
@@ -3123,7 +3100,7 @@ class PlotWidget(qt.QMainWindow):
# Panning with arrow keys
def isPanWithArrowKeys(self):
- """Returns whether or not panning the graph with arrow keys is enable.
+ """Returns whether or not panning the graph with arrow keys is enabled.
See :meth:`setPanWithArrowKeys`.
"""
diff --git a/silx/gui/plot/PlotWindow.py b/silx/gui/plot/PlotWindow.py
index a39430e..0196050 100644
--- a/silx/gui/plot/PlotWindow.py
+++ b/silx/gui/plot/PlotWindow.py
@@ -122,7 +122,7 @@ class PlotWindow(PlotWidget):
self._curvesROIDockWidget = None
self._maskToolsDockWidget = None
self._consoleDockWidget = None
- self._statsWidget = None
+ self._statsDockWidget = None
# Create color bar, hidden by default for backward compatibility
self._colorbar = ColorBarWidget(parent=self, plot=self)
@@ -485,6 +485,22 @@ class PlotWindow(PlotWidget):
self.tabifyDockWidget(self._dockWidgets[0],
dock_widget)
+ def __handleFirstDockWidgetShow(self, visible):
+ """Handle QDockWidget.visibilityChanged
+
+ It calls :meth:`addTabbedDockWidget` for the `sender` widget.
+ This allows to call `addTabbedDockWidget` lazily.
+
+ It disconnect itself from the signal once done.
+
+ :param bool visible:
+ """
+ if visible:
+ dockWidget = self.sender()
+ dockWidget.visibilityChanged.disconnect(
+ self.__handleFirstDockWidgetShow)
+ self.addTabbedDockWidget(dockWidget)
+
def getColorBarWidget(self):
"""Returns the embedded :class:`ColorBarWidget` widget.
@@ -499,7 +515,8 @@ class PlotWindow(PlotWidget):
if self._legendsDockWidget is None:
self._legendsDockWidget = LegendsDockWidget(plot=self)
self._legendsDockWidget.hide()
- self.addTabbedDockWidget(self._legendsDockWidget)
+ self._legendsDockWidget.visibilityChanged.connect(
+ self.__handleFirstDockWidgetShow)
return self._legendsDockWidget
def getCurvesRoiDockWidget(self):
@@ -509,7 +526,8 @@ class PlotWindow(PlotWidget):
self._curvesROIDockWidget = CurvesROIDockWidget(
plot=self, name='Regions Of Interest')
self._curvesROIDockWidget.hide()
- self.addTabbedDockWidget(self._curvesROIDockWidget)
+ self._curvesROIDockWidget.visibilityChanged.connect(
+ self.__handleFirstDockWidgetShow)
return self._curvesROIDockWidget
def getCurvesRoiWidget(self):
@@ -529,7 +547,8 @@ class PlotWindow(PlotWidget):
self._maskToolsDockWidget = MaskToolsDockWidget(
plot=self, name='Mask')
self._maskToolsDockWidget.hide()
- self.addTabbedDockWidget(self._maskToolsDockWidget)
+ self._maskToolsDockWidget.visibilityChanged.connect(
+ self.__handleFirstDockWidgetShow)
return self._maskToolsDockWidget
def getStatsWidget(self):
@@ -537,16 +556,18 @@ class PlotWindow(PlotWidget):
:rtype: BasicStatsWidget
"""
- if self._statsWidget is None:
- dockWidget = qt.QDockWidget(parent=self)
- dockWidget.setWindowTitle("Curves stats")
- dockWidget.layout().setContentsMargins(0, 0, 0, 0)
- self._statsWidget = BasicStatsWidget(parent=self, plot=self)
- self._statsWidget.sigVisibilityChanged.connect(self.getStatsAction().setChecked)
- dockWidget.setWidget(self._statsWidget)
- dockWidget.hide()
- self.addTabbedDockWidget(dockWidget)
- return self._statsWidget
+ if self._statsDockWidget is None:
+ self._statsDockWidget = qt.QDockWidget()
+ self._statsDockWidget.setWindowTitle("Curves stats")
+ self._statsDockWidget.layout().setContentsMargins(0, 0, 0, 0)
+ statsWidget = BasicStatsWidget(parent=self, plot=self)
+ self._statsDockWidget.setWidget(statsWidget)
+ statsWidget.sigVisibilityChanged.connect(
+ self.getStatsAction().setChecked)
+ self._statsDockWidget.hide()
+ self._statsDockWidget.visibilityChanged.connect(
+ self.__handleFirstDockWidgetShow)
+ return self._statsDockWidget.widget()
# getters for actions
@property
diff --git a/silx/gui/plot/ScatterMaskToolsWidget.py b/silx/gui/plot/ScatterMaskToolsWidget.py
index 0c6797f..9a15763 100644
--- a/silx/gui/plot/ScatterMaskToolsWidget.py
+++ b/silx/gui/plot/ScatterMaskToolsWidget.py
@@ -276,7 +276,12 @@ class ScatterMaskToolsWidget(BaseMaskToolsWidget):
self.plot.sigActiveScatterChanged.connect(self._activeScatterChanged)
def hideEvent(self, event):
- self.plot.sigActiveScatterChanged.disconnect(self._activeScatterChanged)
+ try:
+ # if the method is not connected this raises a TypeError and there is no way
+ # to know the connected slots
+ self.plot.sigActiveScatterChanged.disconnect(self._activeScatterChanged)
+ except (RuntimeError, TypeError):
+ _logger.info(sys.exc_info()[1])
if not self.browseAction.isChecked():
self.browseAction.trigger() # Disable drawing tool
diff --git a/silx/gui/plot/ScatterView.py b/silx/gui/plot/ScatterView.py
index 1d015d4..bdbf3ab 100644
--- a/silx/gui/plot/ScatterView.py
+++ b/silx/gui/plot/ScatterView.py
@@ -79,7 +79,7 @@ class ScatterView(qt.QMainWindow):
self._plot = weakref.ref(plot)
# Add an empty scatter
- plot.addScatter(x=(), y=(), value=(), legend=self._SCATTER_LEGEND)
+ self.__createEmptyScatter()
# Create colorbar widget with white background
self._colorbar = ColorBarWidget(parent=self, plot=plot)
@@ -145,6 +145,21 @@ class ScatterView(qt.QMainWindow):
for action in toolbar.actions():
self.addAction(action)
+
+ def __createEmptyScatter(self):
+ """Create an empty scatter item that is used to display the data
+
+ :rtype: ~silx.gui.plot.items.Scatter
+ """
+ plot = self.getPlotWidget()
+ plot.addScatter(x=(), y=(), value=(), legend=self._SCATTER_LEGEND)
+ scatter = plot._getItem(
+ kind='scatter', legend=self._SCATTER_LEGEND)
+ # Profile is not selectable,
+ # so it does not interfere with profile interaction
+ scatter._setSelectable(False)
+ return scatter
+
def _pickScatterData(self, x, y):
"""Get data and index and value of top most scatter plot at position (x, y)
@@ -162,17 +177,19 @@ class ScatterView(qt.QMainWindow):
pixelPos = plot.dataToPixel(x, y)
if pixelPos is not None:
# Start from top-most item
- for item, indices in reversed(plot._pick(*pixelPos)):
- if isinstance(item, items.Scatter):
- # Get last index
- # with matplotlib it should be the top-most point
- dataIndex = indices[-1]
- self.__pickingCache = (
- dataIndex,
- item.getXData(copy=False)[dataIndex],
- item.getYData(copy=False)[dataIndex],
- item.getValueData(copy=False)[dataIndex])
- break
+ result = plot._pickTopMost(
+ pixelPos[0], pixelPos[1],
+ lambda item: isinstance(item, items.Scatter))
+ if result is not None:
+ # Get last index
+ # with matplotlib it should be the top-most point
+ dataIndex = result.getIndices(copy=False)[-1]
+ item = result.getItem()
+ self.__pickingCache = (
+ dataIndex,
+ item.getXData(copy=False)[dataIndex],
+ item.getYData(copy=False)[dataIndex],
+ item.getValueData(copy=False)[dataIndex])
return self.__pickingCache
@@ -343,9 +360,7 @@ class ScatterView(qt.QMainWindow):
plot = self.getPlotWidget()
scatter = plot._getItem(kind='scatter', legend=self._SCATTER_LEGEND)
if scatter is None: # Resilient to call to PlotWidget API (e.g., clear)
- plot.addScatter(x=(), y=(), value=(), legend=self._SCATTER_LEGEND)
- scatter = plot._getItem(
- kind='scatter', legend=self._SCATTER_LEGEND)
+ scatter = self.__createEmptyScatter()
return scatter
# Convenient proxies
diff --git a/silx/gui/plot/StackView.py b/silx/gui/plot/StackView.py
index 2a3d7e8..7e4c389 100644
--- a/silx/gui/plot/StackView.py
+++ b/silx/gui/plot/StackView.py
@@ -85,6 +85,8 @@ from .Profile import Profile3DToolBar
from ..widgets.FrameBrowser import HorizontalSliderWithBrowser
from silx.gui.plot.actions import control as actions_control
+from silx.gui.plot.actions import io as silx_io
+from silx.io.nxdata import save_NXdata
from silx.utils.array_like import DatasetView, ListOfImages
from silx.math import calibration
from silx.utils.deprecation import deprecated_warning
@@ -160,6 +162,9 @@ class StackView(qt.QMainWindow):
This signal provides the current frame number.
"""
+ IMAGE_STACK_FILTER_NXDATA = 'Stack of images as NXdata (%s)' % silx_io._NEXUS_HDF5_EXT_STR
+
+
def __init__(self, parent=None, resetzoom=True, backend=None,
autoScale=False, logScale=False, grid=False,
colormap=True, aspectRatio=True, yinverted=True,
@@ -226,6 +231,7 @@ class StackView(qt.QMainWindow):
self._plot.getXAxis().setLabel('Columns')
self._plot.getYAxis().setLabel('Rows')
self._plot.sigPlotSignal.connect(self._plotCallback)
+ self._plot.getSaveAction().setFileFilter('image', self.IMAGE_STACK_FILTER_NXDATA, func=self._saveImageStack, appendToFile=True)
self.__planeSelection = PlanesWidget(self._plot)
self.__planeSelection.sigPlaneSelectionChanged.connect(self.setPerspective)
@@ -253,6 +259,25 @@ class StackView(qt.QMainWindow):
self.__planeSelection.sigPlaneSelectionChanged.connect(
self._plot.profile.clearProfile)
+ def _saveImageStack(self, plot, filename, nameFilter):
+ """Save all images from the stack into a volume.
+
+ :param str filename: The name of the file to write
+ :param str nameFilter: The selected name filter
+ :return: False if format is not supported or save failed,
+ True otherwise.
+ :raises: ValueError if nameFilter is invalid
+ """
+ if not nameFilter == self.IMAGE_STACK_FILTER_NXDATA:
+ raise ValueError('Wrong callback')
+ entryPath = silx_io.SaveAction._selectWriteableOutputGroup(filename, parent=self)
+ if entryPath is None:
+ return False
+ return save_NXdata(filename,
+ nxentry_name=entryPath,
+ signal=self.getStack(copy=False, returnNumpyArray=True)[0],
+ signal_name="image_stack")
+
def _addColorBarAction(self):
self._plot.getColorBarWidget().setVisible(True)
actions = self._plot.toolBar().actions()
@@ -517,7 +542,12 @@ class StackView(qt.QMainWindow):
# This call to setColormap redefines the meaning of autoscale
# for 3D volume: take global min/max rather than frame min/max
if self.__autoscaleCmap:
- self.setColormap(autoscale=True)
+ # note: there is no real autoscale in the stack widget, it is more
+ # like a hack computing stack min and max
+ colormap = self.getColormap()
+ _vmin, _vmax = colormap.getColormapRange(data=self._stack)
+ colormap.setVRange(_vmin, _vmax)
+ self.setColormap(colormap=colormap)
# init plot
self._plot.addImage(self.__transposed_view[0, :, :],
diff --git a/silx/gui/plot/StatsWidget.py b/silx/gui/plot/StatsWidget.py
index 5e2dc58..80bc05d 100644
--- a/silx/gui/plot/StatsWidget.py
+++ b/silx/gui/plot/StatsWidget.py
@@ -1323,6 +1323,7 @@ class _BaseLineStatsWidget(_StatsWidgetBase, qt.QWidget):
def setStats(self, statsHandler):
"""Set which stats to display and the associated formatting.
+
:param StatsHandler statsHandler:
Set the statistics to be displayed and how to format them using
"""
@@ -1541,12 +1542,12 @@ class BasicGridStatsWidget(qt.QWidget):
only visible ones.
:param int statsPerLine: number of statistic to be displayed per line
- .. snapshotqt:: img/_BasicGridStatsWidget.png
+ .. snapshotqt:: img/BasicGridStatsWidget.png
:width: 600px
:align: center
from silx.gui.plot import Plot1D
- from silx.gui.plot.StatsWidget import _BasicGridStatsWidget
+ from silx.gui.plot.StatsWidget import BasicGridStatsWidget
plot = Plot1D()
x = range(100)
@@ -1554,7 +1555,7 @@ class BasicGridStatsWidget(qt.QWidget):
plot.addCurve(x, y, legend='curve_0')
plot.setActiveCurve('curve_0')
- widget = _BasicGridStatsWidget(plot=plot, kind='curve')
+ widget = BasicGridStatsWidget(plot=plot, kind='curve')
widget.show()
"""
diff --git a/silx/gui/plot/actions/control.py b/silx/gui/plot/actions/control.py
index ec4a3de..e2fa6b1 100644..100755
--- a/silx/gui/plot/actions/control.py
+++ b/silx/gui/plot/actions/control.py
@@ -360,8 +360,8 @@ class ColormapAction(PlotAction):
# Run the dialog listening to colormap change
if checked is True:
- self._dialog.show()
self._updateColormap()
+ self._dialog.show()
else:
self._dialog.hide()
diff --git a/silx/gui/plot/actions/fit.py b/silx/gui/plot/actions/fit.py
index cb70733..6fc5c75 100644
--- a/silx/gui/plot/actions/fit.py
+++ b/silx/gui/plot/actions/fit.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2004-2017 European Synchrotron Radiation Facility
+# Copyright (c) 2004-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -98,18 +98,15 @@ class FitAction(PlotToolAction):
plot, icon='math-fit', text='Fit curve',
tooltip='Open a fit dialog',
parent=parent)
- self.fit_widget = None
def _createToolWindow(self):
- window = qt.QMainWindow(parent=self.plot)
# import done here rather than at module level to avoid circular import
# FitWidget -> BackgroundWidget -> PlotWindow -> actions -> fit -> FitWidget
from ...fit.FitWidget import FitWidget
- fit_widget = FitWidget(parent=window)
- window.setCentralWidget(fit_widget)
- fit_widget.guibuttons.DismissButton.clicked.connect(window.close)
- fit_widget.sigFitWidgetSignal.connect(self.handle_signal)
- self.fit_widget = fit_widget
+
+ window = FitWidget(parent=self.plot)
+ window.setWindowFlags(qt.Qt.Window)
+ window.sigFitWidgetSignal.connect(self.handle_signal)
return window
def _connectPlot(self, window):
@@ -158,8 +155,8 @@ class FitAction(PlotToolAction):
self.x = item.getXData(copy=False)
self.y = item.getYData(copy=False)
- self.fit_widget.setData(self.x, self.y,
- xmin=self.xmin, xmax=self.xmax)
+ window.setData(self.x, self.y,
+ xmin=self.xmin, xmax=self.xmax)
window.setWindowTitle(
"Fitting " + self.legend +
" on x range %f-%f" % (self.xmin, self.xmax))
@@ -171,7 +168,10 @@ class FitAction(PlotToolAction):
fit_curve = self.plot.getCurve(fit_legend)
if ddict["event"] == "FitFinished":
- y_fit = self.fit_widget.fitmanager.gendata()
+ fit_widget = self._getToolWindow()
+ if fit_widget is None:
+ return
+ y_fit = fit_widget.fitmanager.gendata()
if fit_curve is None:
self.plot.addCurve(x_fit, y_fit,
fit_legend,
diff --git a/silx/gui/plot/actions/histogram.py b/silx/gui/plot/actions/histogram.py
index 9181f53..3bb3e6a 100644
--- a/silx/gui/plot/actions/histogram.py
+++ b/silx/gui/plot/actions/histogram.py
@@ -134,6 +134,7 @@ class PixelIntensitiesHistoAction(PlotToolAction):
window = Plot1D(parent=self.plot)
window.setWindowFlags(qt.Qt.Window)
window.setWindowTitle('Image Intensity Histogram')
+ window.setDataMargins(0.1, 0.1, 0.1, 0.1)
window.getXAxis().setLabel("Value")
window.getYAxis().setLabel("Count")
return window
diff --git a/silx/gui/plot/actions/io.py b/silx/gui/plot/actions/io.py
index 09e4a99..43b3b3a 100644
--- a/silx/gui/plot/actions/io.py
+++ b/silx/gui/plot/actions/io.py
@@ -131,6 +131,7 @@ class SaveAction(PlotAction):
IMAGE_FILTER_CSV_TAB = 'Image data as tab-separated CSV (*.csv)'
IMAGE_FILTER_RGB_PNG = 'Image as PNG (*.png)'
IMAGE_FILTER_NXDATA = 'Image as NXdata (%s)' % _NEXUS_HDF5_EXT_STR
+
DEFAULT_IMAGE_FILTERS = (IMAGE_FILTER_EDF,
IMAGE_FILTER_TIFF,
IMAGE_FILTER_NUMPY,
@@ -156,6 +157,8 @@ class SaveAction(PlotAction):
'image': OrderedDict(),
'scatter': OrderedDict()}
+ self._appendFilters = list(self.DEFAULT_APPEND_FILTERS)
+
# Initialize filters
for nameFilter in self.DEFAULT_ALL_FILTERS:
self.setFileFilter(
@@ -185,10 +188,11 @@ class SaveAction(PlotAction):
self.setShortcut(qt.QKeySequence.Save)
self.setShortcutContext(qt.Qt.WidgetShortcut)
- def _errorMessage(self, informativeText=''):
+ @staticmethod
+ def _errorMessage(informativeText='', parent=None):
"""Display an error message."""
# TODO issue with QMessageBox size fixed and too small
- msg = qt.QMessageBox(self.plot)
+ msg = qt.QMessageBox(parent)
msg.setIcon(qt.QMessageBox.Critical)
msg.setInformativeText(informativeText + ' ' + str(sys.exc_info()[1]))
msg.setDetailedText(traceback.format_exc())
@@ -220,7 +224,8 @@ class SaveAction(PlotAction):
ylabel = item.getYLabel() or self.plot.getYAxis().getLabel()
return xlabel, ylabel
- def _selectWriteableOutputGroup(self, filename):
+ @staticmethod
+ def _selectWriteableOutputGroup(filename, parent):
if os.path.exists(filename) and os.path.isfile(filename) \
and os.access(filename, os.W_OK):
entryPath = selectOutputGroup(filename)
@@ -232,11 +237,11 @@ class SaveAction(PlotAction):
# create new entry in new file
return "/entry"
else:
- self._errorMessage('Save failed (file access issue)\n')
+ SaveAction._errorMessage('Save failed (file access issue)\n', parent=parent)
return None
def _saveCurveAsNXdata(self, curve, filename):
- entryPath = self._selectWriteableOutputGroup(filename)
+ entryPath = self._selectWriteableOutputGroup(filename, parent=self.plot)
if entryPath is None:
return False
@@ -273,7 +278,7 @@ class SaveAction(PlotAction):
if curve is None:
curves = plot.getAllCurves()
if not curves:
- self._errorMessage("No curve to be saved")
+ self._errorMessage("No curve to be saved", parent=self.plot)
return False
curve = curves[0]
@@ -299,7 +304,7 @@ class SaveAction(PlotAction):
fmt=fmt, csvdelim=csvdelim,
autoheader=autoheader)
except IOError:
- self._errorMessage('Save failed\n')
+ self._errorMessage('Save failed\n', parent=self.plot)
return False
return True
@@ -317,7 +322,7 @@ class SaveAction(PlotAction):
curves = plot.getAllCurves()
if not curves:
- self._errorMessage("No curves to be saved")
+ self._errorMessage("No curves to be saved", parent=self.plot)
return False
curve = curves[0]
@@ -334,7 +339,7 @@ class SaveAction(PlotAction):
write_file_header=True,
close_file=False)
except IOError:
- self._errorMessage('Save failed\n')
+ self._errorMessage('Save failed\n', parent=self.plot)
return False
for curve in curves[1:]:
@@ -351,7 +356,7 @@ class SaveAction(PlotAction):
write_file_header=False,
close_file=False)
except IOError:
- self._errorMessage('Save failed\n')
+ self._errorMessage('Save failed\n', parent=self.plot)
return False
specfile.close()
@@ -391,12 +396,12 @@ class SaveAction(PlotAction):
try:
numpy.save(filename, data)
except IOError:
- self._errorMessage('Save failed\n')
+ self._errorMessage('Save failed\n', parent=self.plot)
return False
return True
elif nameFilter == self.IMAGE_FILTER_NXDATA:
- entryPath = self._selectWriteableOutputGroup(filename)
+ entryPath = self._selectWriteableOutputGroup(filename, parent=self.plot)
if entryPath is None:
return False
xorigin, yorigin = image.getOrigin()
@@ -438,7 +443,7 @@ class SaveAction(PlotAction):
autoheader=True)
except IOError:
- self._errorMessage('Save failed\n')
+ self._errorMessage('Save failed\n', parent=self.plot)
return False
return True
@@ -471,7 +476,7 @@ class SaveAction(PlotAction):
return False
if nameFilter == self.SCATTER_FILTER_NXDATA:
- entryPath = self._selectWriteableOutputGroup(filename)
+ entryPath = self._selectWriteableOutputGroup(filename, parent=self.plot)
if entryPath is None:
return False
scatter = plot.getScatter()
@@ -502,7 +507,7 @@ class SaveAction(PlotAction):
axes_errors=[xerror, yerror],
title=plot.getGraphTitle())
- def setFileFilter(self, dataKind, nameFilter, func, index=None):
+ def setFileFilter(self, dataKind, nameFilter, func, index=None, appendToFile=False):
"""Set a name filter to add/replace a file format support
:param str dataKind:
@@ -513,10 +518,15 @@ class SaveAction(PlotAction):
:param callable func: The function to call to perform saving.
Expected signature is:
bool func(PlotWidget plot, str filename, str nameFilter)
+ :param bool appendToFile: True to append the data into the selected
+ file.
:param integer index: Index of the filter in the final list (or None)
"""
assert dataKind in ('all', 'curve', 'curves', 'image', 'scatter')
+ if appendToFile:
+ self._appendFilters.append(nameFilter)
+
# first append or replace the new filter to prevent colissions
self._filters[dataKind][nameFilter] = func
if index is None:
@@ -601,7 +611,7 @@ class SaveAction(PlotAction):
def onFilterSelection(filt_):
# disable overwrite confirmation for NXdata types,
# because we append the data to existing files
- if filt_ in self.DEFAULT_APPEND_FILTERS:
+ if filt_ in self._appendFilters:
dialog.setOption(dialog.DontConfirmOverwrite)
else:
dialog.setOption(dialog.DontConfirmOverwrite, False)
diff --git a/silx/gui/plot/backends/BackendBase.py b/silx/gui/plot/backends/BackendBase.py
index af37543..75d999b 100644..100755
--- a/silx/gui/plot/backends/BackendBase.py
+++ b/silx/gui/plot/backends/BackendBase.py
@@ -97,16 +97,15 @@ class BackendBase(object):
# Add methods
- def addCurve(self, x, y, legend,
+ def addCurve(self, x, y,
color, symbol, linewidth, linestyle,
yaxis,
- xerror, yerror, z, selectable,
- fill, alpha, symbolsize):
+ xerror, yerror, z,
+ fill, alpha, symbolsize, baseline):
"""Add a 1D curve given by x an y to the graph.
:param numpy.ndarray x: The data corresponding to the x axis
:param numpy.ndarray y: The data corresponding to the y axis
- :param str legend: The legend to be associated to the curve
:param color: color(s) to be used
:type color: string ("#RRGGBB") or (npoints, 4) unsigned byte array or
one of the predefined color names defined in colors.py
@@ -136,24 +135,21 @@ class BackendBase(object):
:param yerror: Values with the uncertainties on the y values
:type yerror: numpy.ndarray or None
:param int z: Layer on which to draw the cuve
- :param bool selectable: indicate if the curve can be selected
:param bool fill: True to fill the curve, False otherwise
:param float alpha: Curve opacity, as a float in [0., 1.]
:param float symbolsize: Size of the symbol (if any) drawn
at each (x, y) position.
:returns: The handle used by the backend to univocally access the curve
"""
- return legend
+ return object()
- def addImage(self, data, legend,
+ def addImage(self, data,
origin, scale, z,
- selectable, draggable,
colormap, alpha):
"""Add an image to the plot.
:param numpy.ndarray data: (nrows, ncolumns) data or
(nrows, ncolumns, RGBA) ubyte array
- :param str legend: The legend to be associated to the image
:param origin: (origin X, origin Y) of the data.
Default: (0., 0.)
:type origin: 2-tuple of float
@@ -161,39 +157,34 @@ class BackendBase(object):
Default: (1., 1.)
:type scale: 2-tuple of float
:param int z: Layer on which to draw the image
- :param bool selectable: indicate if the image can be selected
- :param bool draggable: indicate if the image can be moved
:param ~silx.gui.colors.Colormap colormap: Colormap object to use.
Ignored if data is RGB(A).
:param float alpha: Opacity of the image, as a float in range [0, 1].
:returns: The handle used by the backend to univocally access the image
"""
- return legend
+ return object()
- def addTriangles(self, x, y, triangles, legend,
- color, z, selectable, alpha):
+ def addTriangles(self, x, y, triangles,
+ color, z, alpha):
"""Add a set of triangles.
:param numpy.ndarray x: The data corresponding to the x axis
:param numpy.ndarray y: The data corresponding to the y axis
:param numpy.ndarray triangles: The indices to make triangles
as a (Ntriangle, 3) array
- :param str legend: The legend to be associated to the curve
:param numpy.ndarray color: color(s) as (npoints, 4) array
:param int z: Layer on which to draw the cuve
- :param bool selectable: indicate if the curve can be selected
:param float alpha: Opacity as a float in [0., 1.]
:returns: The triangles' unique identifier used by the backend
"""
- return legend
+ return object()
- def addItem(self, x, y, legend, shape, color, fill, overlay, z,
+ def addItem(self, x, y, shape, color, fill, overlay, z,
linestyle, linewidth, linebgcolor):
"""Add an item (i.e. a shape) to the plot.
:param numpy.ndarray x: The X coords of the points of the shape
:param numpy.ndarray y: The Y coords of the points of the shape
- :param str legend: The legend to be associated to the item
:param str shape: Type of item to be drawn in
hline, polygon, rectangle, vline, polylines
:param str color: Color of the item
@@ -215,22 +206,18 @@ class BackendBase(object):
'#FF0000'. It is used to draw dotted line using a second color.
:returns: The handle used by the backend to univocally access the item
"""
- return legend
+ return object()
- def addMarker(self, x, y, legend, text, color,
- selectable, draggable,
- symbol, linestyle, linewidth, constraint):
+ def addMarker(self, x, y, text, color,
+ symbol, linestyle, linewidth, constraint, yaxis):
"""Add a point, vertical line or horizontal line marker to the plot.
:param float x: Horizontal position of the marker in graph coordinates.
If None, the marker is a horizontal line.
:param float y: Vertical position of the marker in graph coordinates.
If None, the marker is a vertical line.
- :param str legend: Legend associated to the marker
:param str text: Text associated to the marker (or None for no text)
:param str color: Color to be used for instance 'blue', 'b', '#FF0000'
- :param bool selectable: indicate if the marker can be selected
- :param bool draggable: indicate if the marker can be moved
:param str symbol: Symbol representing the marker.
Only relevant for point markers where X and Y are not None.
Value in:
@@ -257,13 +244,13 @@ class BackendBase(object):
dragging operations or None for no filter.
This function is called each time a marker is
moved.
- This parameter is only used if draggable is True.
:type constraint: None or a callable that takes the coordinates of
the current cursor position in the plot as input
and that returns the filtered coordinates.
+ :param str yaxis: The Y axis this marker belongs to in: 'left', 'right'
:return: Handle used by the backend to univocally access the marker
"""
- return legend
+ return object()
# Remove methods
@@ -307,22 +294,40 @@ class BackendBase(object):
"""
pass
- def pickItems(self, x, y, kinds):
- """Get a list of items at a pixel position.
+ def getItemsFromBackToFront(self, condition=None):
+ """Returns the list of plot items order as rendered by the backend.
+
+ This is the order used for rendering.
+ By default, it takes into account overlays, z value and order of addition of items,
+ but backends can override it.
+
+ :param callable condition:
+ Callable taking an item as input and returning False for items to skip.
+ If None (default), no item is skipped.
+ :rtype: List[~silx.gui.plot.items.Item]
+ """
+ # Sort items: Overlays first, then others
+ # and in each category ordered by z and then by order of addition
+ # as content keeps this order.
+ content = self._plot.getItems()
+ if condition is not None:
+ content = [item for item in content if condition(item)]
+
+ return sorted(
+ content,
+ key=lambda i: ((1 if i.isOverlay() else 0), i.getZValue()))
+
+ def pickItem(self, x, y, item):
+ """Return picked indices if any, or None.
:param float x: The x pixel coord where to pick.
:param float y: The y pixel coord where to pick.
- :param List[str] kind: List of item kinds to pick.
- Supported kinds: 'marker', 'curve', 'image'.
- :return: All picked items from back to front.
- One dict per item,
- with 'kind' key in 'curve', 'marker', 'image';
- 'legend' key, the item legend.
- and for curves, 'xdata' and 'ydata' keys storing picked
- position on the curve.
- :rtype: list of dict
- """
- return []
+ :param item: A backend item created with add* methods.
+ :return: None if item was not picked, else returns
+ picked indices information.
+ :rtype: Union[None,List]
+ """
+ return None
# Update curve
diff --git a/silx/gui/plot/backends/BackendMatplotlib.py b/silx/gui/plot/backends/BackendMatplotlib.py
index 7739329..075f6aa 100644..100755
--- a/silx/gui/plot/backends/BackendMatplotlib.py
+++ b/silx/gui/plot/backends/BackendMatplotlib.py
@@ -56,12 +56,13 @@ from matplotlib.collections import PathCollection, LineCollection
from matplotlib.ticker import Formatter, ScalarFormatter, Locator
from matplotlib.tri import Triangulation
from matplotlib.collections import TriMesh
+from matplotlib import path as mpath
from . import BackendBase
+from .. import items
from .._utils import FLOAT32_MINPOS
from .._utils.dtime_ticklayout import calcTicks, bestFormatString, timestamp
-
_PATCH_LINESTYLE = {
"-": 'solid',
"--": 'dashed',
@@ -72,11 +73,54 @@ _PATCH_LINESTYLE = {
}
"""Patches do not uses the same matplotlib syntax"""
+_MARKER_PATHS = {}
+"""Store cached extra marker paths"""
+
+_SPECIAL_MARKERS = {
+ 'tickleft': 0,
+ 'tickright': 1,
+ 'tickup': 2,
+ 'tickdown': 3,
+ 'caretleft': 4,
+ 'caretright': 5,
+ 'caretup': 6,
+ 'caretdown': 7,
+}
+
def normalize_linestyle(linestyle):
"""Normalize known old-style linestyle, else return the provided value."""
return _PATCH_LINESTYLE.get(linestyle, linestyle)
+def get_path_from_symbol(symbol):
+ """Get the path representation of a symbol, else None if
+ it is not provided.
+
+ :param str symbol: Symbol description used by silx
+ :rtype: Union[None,matplotlib.path.Path]
+ """
+ if symbol == u'\u2665':
+ path = _MARKER_PATHS.get(symbol, None)
+ if path is not None:
+ return path
+ vertices = numpy.array([
+ [0,-99],
+ [31,-73], [47,-55], [55,-46],
+ [63,-37], [94,-2], [94,33],
+ [94,69], [71,89], [47,89],
+ [24,89], [8,74], [0,58],
+ [-8,74], [-24,89], [-47,89],
+ [-71,89], [-94,69], [-94,33],
+ [-94,-2], [-63,-37], [-55,-46],
+ [-47,-55], [-31,-73], [0,-99],
+ [0,-99]])
+ codes = [mpath.Path.CURVE4] * len(vertices)
+ codes[0] = mpath.Path.MOVETO
+ codes[-1] = mpath.Path.CLOSEPOLY
+ path = mpath.Path(vertices, codes)
+ _MARKER_PATHS[symbol] = path
+ return path
+ return None
class NiceDateLocator(Locator):
"""
@@ -162,7 +206,53 @@ class NiceAutoDateFormatter(Formatter):
return tickStr
-class _MarkerContainer(Container):
+class _PickableContainer(Container):
+ """Artists container with a :meth:`contains` method"""
+
+ def __init__(self, *args, **kwargs):
+ Container.__init__(self, *args, **kwargs)
+ self.__zorder = None
+
+ @property
+ def axes(self):
+ """Mimin Artist.axes"""
+ for child in self.get_children():
+ if hasattr(child, 'axes'):
+ return child.axes
+ return None
+
+ def draw(self, *args, **kwargs):
+ """artist-like draw to broadcast draw to children"""
+ for child in self.get_children():
+ child.draw(*args, **kwargs)
+
+ def get_zorder(self):
+ """Mimic Artist.get_zorder"""
+ return self.__zorder
+
+ def set_zorder(self, z):
+ """Mimic Artist.set_zorder to broadcast to children"""
+ if z != self.__zorder:
+ self.__zorder = z
+ for child in self.get_children():
+ child.set_zorder(z)
+
+ def contains(self, mouseevent):
+ """Mimic Artist.contains, and call it on all children.
+
+ :param mouseevent:
+ :return: Picking status and associated information as a dict
+ :rtype: (bool,dict)
+ """
+ # Goes through children from front to back and return first picked one.
+ for child in reversed(self.get_children()):
+ picked, info = child.contains(mouseevent)
+ if picked:
+ return picked, info
+ return False, {}
+
+
+class _MarkerContainer(_PickableContainer):
"""Marker artists container supporting draw/remove and text position update
:param artists:
@@ -173,13 +263,14 @@ class _MarkerContainer(Container):
:param y: Y coordinate of the marker (None for vertical lines)
"""
- def __init__(self, artists, x, y):
+ def __init__(self, artists, x, y, yAxis):
self.line = artists[0]
self.text = artists[1] if len(artists) > 1 else None
self.x = x
self.y = y
+ self.yAxis = yAxis
- Container.__init__(self, artists)
+ _PickableContainer.__init__(self, artists)
def draw(self, *args, **kwargs):
"""artist-like draw to broadcast draw to line and text"""
@@ -214,6 +305,15 @@ class _MarkerContainer(Container):
xmax -= 0.005 * delta
self.text.set_x(xmax)
+ def contains(self, mouseevent):
+ """Mimic Artist.contains, and call it on the line Artist.
+
+ :param mouseevent:
+ :return: Picking status and associated information as a dict
+ :rtype: (bool,dict)
+ """
+ return self.line.contains(mouseevent)
+
class _DoubleColoredLinePatch(matplotlib.patches.Patch):
"""Matplotlib patch to display any patch using double color."""
@@ -294,7 +394,11 @@ class BackendMatplotlib(BackendBase.BackendBase):
self.ax2 = self.ax.twinx()
self.ax2.set_label("right")
# Make sure background of Axes is displayed
- self.ax2.patch.set_visible(True)
+ self.ax2.patch.set_visible(False)
+ self.ax.patch.set_visible(True)
+
+ # Set axis zorder=0.5 so grid is displayed at 0.5
+ self.ax.set_axisbelow(True)
# disable the use of offsets
try:
@@ -306,10 +410,8 @@ class BackendMatplotlib(BackendBase.BackendBase):
_logger.warning('Cannot disabled axes offsets in %s '
% matplotlib.__version__)
- # critical for picking!!!!
- self.ax2.set_zorder(0)
self.ax2.set_autoscaley_on(True)
- self.ax.set_zorder(1)
+
# this works but the figure color is left
if self._matplotlibVersion < _parse_version('2'):
self.ax.set_axis_bgcolor('none')
@@ -317,7 +419,6 @@ class BackendMatplotlib(BackendBase.BackendBase):
self.ax.set_facecolor('none')
self.fig.sca(self.ax)
- self._overlays = set()
self._background = None
self._colormaps = {}
@@ -327,15 +428,67 @@ class BackendMatplotlib(BackendBase.BackendBase):
self._enableAxis('right', False)
self._isXAxisTimeSeries = False
+ def getItemsFromBackToFront(self, condition=None):
+ """Order as BackendBase + take into account matplotlib Axes structure"""
+ def axesOrder(item):
+ if item.isOverlay():
+ return 2
+ elif isinstance(item, items.YAxisMixIn) and item.getYAxis() == 'right':
+ return 1
+ else:
+ return 0
+
+ return sorted(
+ BackendBase.BackendBase.getItemsFromBackToFront(
+ self, condition=condition),
+ key=axesOrder)
+
+ def _overlayItems(self):
+ """Generator of backend renderer for overlay items"""
+ for item in self._plot.getItems():
+ if (item.isOverlay() and
+ item.isVisible() and
+ item._backendRenderer is not None):
+ yield item._backendRenderer
+
+ def _hasOverlays(self):
+ """Returns whether there is an overlay layer or not.
+
+ The overlay layers contains overlay items and the crosshair.
+
+ :rtype: bool
+ """
+ if self._graphCursor:
+ return True # There is the crosshair
+
+ for item in self._overlayItems():
+ return True # There is at least one overlay item
+ return False
+
# Add methods
- def addCurve(self, x, y, legend,
+ def _getMarkerFromSymbol(self, symbol):
+ """Returns a marker that can be displayed by matplotlib.
+
+ :param str symbol: A symbol description used by silx
+ :rtype: Union[str,int,matplotlib.path.Path]
+ """
+ path = get_path_from_symbol(symbol)
+ if path is not None:
+ return path
+ num = _SPECIAL_MARKERS.get(symbol, None)
+ if num is not None:
+ return num
+ # This symbol must be supported by matplotlib
+ return symbol
+
+ def addCurve(self, x, y,
color, symbol, linewidth, linestyle,
yaxis,
- xerror, yerror, z, selectable,
- fill, alpha, symbolsize):
- for parameter in (x, y, legend, color, symbol, linewidth, linestyle,
- yaxis, z, selectable, fill, alpha, symbolsize):
+ xerror, yerror, z,
+ fill, alpha, symbolsize, baseline):
+ for parameter in (x, y, color, symbol, linewidth, linestyle,
+ yaxis, z, fill, alpha, symbolsize):
assert parameter is not None
assert yaxis in ('left', 'right')
@@ -349,7 +502,7 @@ class BackendMatplotlib(BackendBase.BackendBase):
else:
axes = self.ax
- picker = 3 if selectable else None
+ picker = 3
artists = [] # All the artists composing the curve
@@ -368,7 +521,7 @@ class BackendMatplotlib(BackendBase.BackendBase):
yerror.shape[1] == 1):
yerror = numpy.ravel(yerror)
- errorbars = axes.errorbar(x, y, label=legend,
+ errorbars = axes.errorbar(x, y,
xerr=xerror, yerr=yerror,
linestyle=' ', color=errorbarColor)
artists += list(errorbars.get_children())
@@ -383,7 +536,7 @@ class BackendMatplotlib(BackendBase.BackendBase):
if linestyle not in ["", " ", None]:
# scatter plot with an actual line ...
# we need to assign a color ...
- curveList = axes.plot(x, y, label=legend,
+ curveList = axes.plot(x, y,
linestyle=linestyle,
color=actualColor[0],
linewidth=linewidth,
@@ -391,21 +544,24 @@ class BackendMatplotlib(BackendBase.BackendBase):
marker=None)
artists += list(curveList)
+ marker = self._getMarkerFromSymbol(symbol)
scatter = axes.scatter(x, y,
- label=legend,
color=actualColor,
- marker=symbol,
+ marker=marker,
picker=picker,
s=symbolsize**2)
artists.append(scatter)
if fill:
+ if baseline is None:
+ _baseline = FLOAT32_MINPOS
+ else:
+ _baseline = baseline
artists.append(axes.fill_between(
- x, FLOAT32_MINPOS, y, facecolor=actualColor[0], linestyle=''))
+ x, _baseline, y, facecolor=actualColor[0], linestyle=''))
else: # Curve
curveList = axes.plot(x, y,
- label=legend,
linestyle=linestyle,
color=color,
linewidth=linewidth,
@@ -415,40 +571,35 @@ class BackendMatplotlib(BackendBase.BackendBase):
artists += list(curveList)
if fill:
+ if baseline is None:
+ _baseline = FLOAT32_MINPOS
+ else:
+ _baseline = baseline
artists.append(
- axes.fill_between(x, FLOAT32_MINPOS, y, facecolor=color))
+ axes.fill_between(x, _baseline, y, facecolor=color))
for artist in artists:
- artist.set_zorder(z)
if alpha < 1:
artist.set_alpha(alpha)
- return Container(artists)
+ return _PickableContainer(artists)
- def addImage(self, data, legend,
- origin, scale, z,
- selectable, draggable,
- colormap, alpha):
+ def addImage(self, data, origin, scale, z, colormap, alpha):
# Non-uniform image
# http://wiki.scipy.org/Cookbook/Histograms
# Non-linear axes
# http://stackoverflow.com/questions/11488800/non-linear-axes-for-imshow-in-matplotlib
- for parameter in (data, legend, origin, scale, z,
- selectable, draggable):
+ for parameter in (data, origin, scale, z):
assert parameter is not None
origin = float(origin[0]), float(origin[1])
scale = float(scale[0]), float(scale[1])
height, width = data.shape[0:2]
- picker = (selectable or draggable)
-
# All image are shown as RGBA image
image = Image(self.ax,
- label="__IMAGE__" + legend,
interpolation='nearest',
- picker=picker,
- zorder=z,
+ picker=True,
origin='lower')
if alpha < 1:
@@ -481,15 +632,10 @@ class BackendMatplotlib(BackendBase.BackendBase):
self.ax.add_artist(image)
return image
- def addTriangles(self, x, y, triangles, legend,
- color, z, selectable, alpha):
- for parameter in (x, y, triangles, legend, color,
- z, selectable, alpha):
+ def addTriangles(self, x, y, triangles, color, z, alpha):
+ for parameter in (x, y, triangles, color, z, alpha):
assert parameter is not None
- # 0 enables picking on filled triangle
- picker = 0 if selectable else None
-
color = numpy.array(color, copy=False)
assert color.ndim == 2 and len(color) == len(x)
@@ -498,16 +644,14 @@ class BackendMatplotlib(BackendBase.BackendBase):
collection = TriMesh(
Triangulation(x, y, triangles),
- label=legend,
alpha=alpha,
- picker=picker,
- zorder=z)
+ picker=0) # 0 enables picking on filled triangle
collection.set_color(color)
self.ax.add_collection(collection)
return collection
- def addItem(self, x, y, legend, shape, color, fill, overlay, z,
+ def addItem(self, x, y, shape, color, fill, overlay, z,
linestyle, linewidth, linebgcolor):
if (linebgcolor is not None and
shape not in ('rectangle', 'polygon', 'polylines')):
@@ -520,20 +664,20 @@ class BackendMatplotlib(BackendBase.BackendBase):
linestyle = normalize_linestyle(linestyle)
if shape == "line":
- item = self.ax.plot(x, y, label=legend, color=color,
+ item = self.ax.plot(x, y, color=color,
linestyle=linestyle, linewidth=linewidth,
marker=None)[0]
elif shape == "hline":
if hasattr(y, "__len__"):
y = y[-1]
- item = self.ax.axhline(y, label=legend, color=color,
+ item = self.ax.axhline(y, color=color,
linestyle=linestyle, linewidth=linewidth)
elif shape == "vline":
if hasattr(x, "__len__"):
x = x[-1]
- item = self.ax.axvline(x, label=legend, color=color,
+ item = self.ax.axvline(x, color=color,
linestyle=linestyle, linewidth=linewidth)
elif shape == 'rectangle':
@@ -568,7 +712,6 @@ class BackendMatplotlib(BackendBase.BackendBase):
item = Polygon(points,
closed=closed,
fill=False,
- label=legend,
color=color,
linestyle=linestyle,
linewidth=linewidth)
@@ -584,30 +727,32 @@ class BackendMatplotlib(BackendBase.BackendBase):
else:
raise NotImplementedError("Unsupported item shape %s" % shape)
- item.set_zorder(z)
-
if overlay:
item.set_animated(True)
- self._overlays.add(item)
return item
- def addMarker(self, x, y, legend, text, color,
- selectable, draggable,
- symbol, linestyle, linewidth, constraint):
- legend = "__MARKER__" + legend
-
+ def addMarker(self, x, y, text, color,
+ symbol, linestyle, linewidth, constraint, yaxis):
textArtist = None
xmin, xmax = self.getGraphXLimits()
- ymin, ymax = self.getGraphYLimits(axis='left')
+ ymin, ymax = self.getGraphYLimits(axis=yaxis)
+
+ if yaxis == 'left':
+ ax = self.ax
+ elif yaxis == 'right':
+ ax = self.ax2
+ else:
+ assert(False)
+ marker = self._getMarkerFromSymbol(symbol)
if x is not None and y is not None:
- line = self.ax.plot(x, y, label=legend,
- linestyle=" ",
- color=color,
- marker=symbol,
- markersize=10.)[-1]
+ line = ax.plot(x, y,
+ linestyle=" ",
+ color=color,
+ marker=marker,
+ markersize=10.)[-1]
if text is not None:
if symbol is None:
@@ -616,43 +761,40 @@ class BackendMatplotlib(BackendBase.BackendBase):
valign = 'top'
text = " " + text
- textArtist = self.ax.text(x, y, text,
- color=color,
- horizontalalignment='left',
- verticalalignment=valign)
+ textArtist = ax.text(x, y, text,
+ color=color,
+ horizontalalignment='left',
+ verticalalignment=valign)
elif x is not None:
- line = self.ax.axvline(x,
- label=legend,
- color=color,
- linewidth=linewidth,
- linestyle=linestyle)
+ line = ax.axvline(x,
+ color=color,
+ linewidth=linewidth,
+ linestyle=linestyle)
if text is not None:
# Y position will be updated in updateMarkerText call
- textArtist = self.ax.text(x, 1., " " + text,
- color=color,
- horizontalalignment='left',
- verticalalignment='top')
+ textArtist = ax.text(x, 1., " " + text,
+ color=color,
+ horizontalalignment='left',
+ verticalalignment='top')
elif y is not None:
- line = self.ax.axhline(y,
- label=legend,
- color=color,
- linewidth=linewidth,
- linestyle=linestyle)
+ line = ax.axhline(y,
+ color=color,
+ linewidth=linewidth,
+ linestyle=linestyle)
if text is not None:
# X position will be updated in updateMarkerText call
- textArtist = self.ax.text(1., y, " " + text,
- color=color,
- horizontalalignment='right',
- verticalalignment='top')
+ textArtist = ax.text(1., y, " " + text,
+ color=color,
+ horizontalalignment='right',
+ verticalalignment='top')
else:
raise RuntimeError('A marker must at least have one coordinate')
- if selectable or draggable:
- line.set_picker(5)
+ line.set_picker(5)
# All markers are overlays
line.set_animated(True)
@@ -660,24 +802,25 @@ class BackendMatplotlib(BackendBase.BackendBase):
textArtist.set_animated(True)
artists = [line] if textArtist is None else [line, textArtist]
- container = _MarkerContainer(artists, x, y)
+ container = _MarkerContainer(artists, x, y, yaxis)
container.updateMarkerText(xmin, xmax, ymin, ymax)
- self._overlays.add(container)
return container
def _updateMarkers(self):
xmin, xmax = self.ax.get_xbound()
- ymin, ymax = self.ax.get_ybound()
- for item in self._overlays:
+ ymin1, ymax1 = self.ax.get_ybound()
+ ymin2, ymax2 = self.ax2.get_ybound()
+ for item in self._overlayItems():
if isinstance(item, _MarkerContainer):
- item.updateMarkerText(xmin, xmax, ymin, ymax)
+ if item.yAxis == 'left':
+ item.updateMarkerText(xmin, xmax, ymin1, ymax1)
+ else:
+ item.updateMarkerText(xmin, xmax, ymin2, ymax2)
# Remove methods
def remove(self, item):
- # Warning: It also needs to remove extra stuff if added as for markers
- self._overlays.discard(item)
try:
item.remove()
except ValueError:
@@ -699,7 +842,7 @@ class BackendMatplotlib(BackendBase.BackendBase):
self._graphCursor = lineh, linev
else:
- if self._graphCursor is not None:
+ if self._graphCursor:
lineh, linev = self._graphCursor
lineh.remove()
linev.remove()
@@ -746,7 +889,37 @@ class BackendMatplotlib(BackendBase.BackendBase):
if not self.ax2.lines:
self._enableAxis('right', False)
+ def _drawOverlays(self):
+ """Draw overlays if any."""
+ def condition(item):
+ return (item.isVisible() and
+ item._backendRenderer is not None and
+ item.isOverlay())
+
+ for item in self.getItemsFromBackToFront(condition=condition):
+ if (isinstance(item, items.YAxisMixIn) and
+ item.getYAxis() == 'right'):
+ axes = self.ax2
+ else:
+ axes = self.ax
+ axes.draw_artist(item._backendRenderer)
+
+ for item in self._graphCursor:
+ self.ax.draw_artist(item)
+
+ def updateZOrder(self):
+ """Reorder all items with z order from 0 to 1"""
+ items = self.getItemsFromBackToFront(
+ lambda item: item.isVisible() and item._backendRenderer is not None)
+ count = len(items)
+ for index, item in enumerate(items):
+ zorder = 1. + index / count
+ if zorder != item._backendRenderer.get_zorder():
+ item._backendRenderer.set_zorder(zorder)
+
def saveGraph(self, fileName, fileFormat, dpi):
+ self.updateZOrder()
+
# fileName can be also a StringIO or file instance
if dpi is not None:
self.fig.savefig(fileName, format=fileFormat, dpi=dpi)
@@ -788,7 +961,9 @@ class BackendMatplotlib(BackendBase.BackendBase):
def getGraphXLimits(self):
if self._dirtyLimits and self.isKeepDataAspectRatio():
- self.replot() # makes sure we get the right limits
+ self.ax.apply_aspect()
+ self.ax2.apply_aspect()
+ self._dirtyLimits = False
return self.ax.get_xbound()
def setGraphXLimits(self, xmin, xmax):
@@ -804,7 +979,9 @@ class BackendMatplotlib(BackendBase.BackendBase):
return None
if self._dirtyLimits and self.isKeepDataAspectRatio():
- self.replot() # makes sure we get the right limits
+ self.ax.apply_aspect()
+ self.ax2.apply_aspect()
+ self._dirtyLimits = False
return ax.get_ybound()
@@ -936,7 +1113,7 @@ class BackendMatplotlib(BackendBase.BackendBase):
return xPixel, yPixel
- def pixelToData(self, x, y, axis, check):
+ def pixelToData(self, x, y, axis):
ax = self.ax2 if axis == "right" else self.ax
# Convert from Qt origin (top) to matplotlib origin (bottom)
@@ -944,14 +1121,6 @@ class BackendMatplotlib(BackendBase.BackendBase):
inv = ax.transData.inverted()
x, y = inv.transform_point((x, y))
-
- if check:
- xmin, xmax = self.getGraphXLimits()
- ymin, ymax = self.getGraphYLimits(axis=axis)
-
- if x > xmax or x < xmin or y > ymax or y < ymin:
- return None # (x, y) is out of plot area
-
return x, y
def getPlotBoundsInPixels(self):
@@ -996,12 +1165,12 @@ class BackendMatplotlib(BackendBase.BackendBase):
else:
dataBackgroundColor = backgroundColor
- if self.ax2.axison:
+ if self.ax.axison:
self.fig.patch.set_facecolor(backgroundColor)
if self._matplotlibVersion < _parse_version('2'):
- self.ax2.set_axis_bgcolor(dataBackgroundColor)
+ self.ax.set_axis_bgcolor(dataBackgroundColor)
else:
- self.ax2.set_facecolor(dataBackgroundColor)
+ self.ax.set_facecolor(dataBackgroundColor)
else:
self.fig.patch.set_facecolor(dataBackgroundColor)
@@ -1033,6 +1202,12 @@ class BackendMatplotlib(BackendBase.BackendBase):
line.set_color(gridColor)
# axes.grid().set_markeredgecolor(gridColor)
+ def setBackgroundColors(self, backgroundColor, dataBackgroundColor):
+ self._synchronizeBackgroundColors()
+
+ def setForegroundColors(self, foregroundColor, gridColor):
+ self._synchronizeForegroundColors()
+
class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib):
"""QWidget matplotlib backend using a QtAgg canvas.
@@ -1079,9 +1254,11 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib):
_MPL_TO_PLOT_BUTTONS = {1: 'left', 2: 'middle', 3: 'right'}
def _onMousePress(self, event):
- self._plot.onMousePress(
- event.x, self._mplQtYAxisCoordConversion(event.y),
- self._MPL_TO_PLOT_BUTTONS[event.button])
+ button = self._MPL_TO_PLOT_BUTTONS.get(event.button, None)
+ if button is not None:
+ self._plot.onMousePress(
+ event.x, self._mplQtYAxisCoordConversion(event.y),
+ button)
def _onMouseMove(self, event):
if self._graphCursor:
@@ -1102,9 +1279,11 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib):
event.x, self._mplQtYAxisCoordConversion(event.y))
def _onMouseRelease(self, event):
- self._plot.onMouseRelease(
- event.x, self._mplQtYAxisCoordConversion(event.y),
- self._MPL_TO_PLOT_BUTTONS[event.button])
+ button = self._MPL_TO_PLOT_BUTTONS.get(event.button, None)
+ if button is not None:
+ self._plot.onMouseRelease(
+ event.x, self._mplQtYAxisCoordConversion(event.y),
+ button)
def _onMouseWheel(self, event):
self._plot.onMouseWheel(
@@ -1116,58 +1295,31 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib):
# picking
- def _onPick(self, event):
- # TODO not very nice and fragile, find a better way?
- # Make a selection according to kind
- if self._picked is None:
- _logger.error('Internal picking error')
- return
-
- label = event.artist.get_label()
- if label.startswith('__MARKER__'):
- self._picked.append({'kind': 'marker', 'legend': label[10:]})
+ def pickItem(self, x, y, item):
+ mouseEvent = MouseEvent(
+ 'button_press_event', self, x, self._mplQtYAxisCoordConversion(y))
+ mouseEvent.inaxes = item.axes
+ picked, info = item.contains(mouseEvent)
- elif label.startswith('__IMAGE__'):
- self._picked.append({'kind': 'image', 'legend': label[9:]})
+ if not picked:
+ return None
- elif isinstance(event.artist, TriMesh):
+ elif isinstance(item, TriMesh):
# Convert selected triangle to data point indices
- triangulation = event.artist._triangulation
- indices = triangulation.get_masked_triangles()[event.ind[0]]
+ triangulation = item._triangulation
+ indices = triangulation.get_masked_triangles()[info['ind'][0]]
# Sort picked triangle points by distance to mouse
# from furthest to closest to put closest point last
# This is to be somewhat consistent with last scatter point
# being the top one.
- dists = ((triangulation.x[indices] - event.mouseevent.xdata) ** 2 +
- (triangulation.y[indices] - event.mouseevent.ydata) ** 2)
- indices = indices[numpy.flip(numpy.argsort(dists))]
-
- self._picked.append({'kind': 'curve', 'legend': label,
- 'indices': indices})
+ xdata, ydata = self.pixelToData(x, y, axis='left')
+ dists = ((triangulation.x[indices] - xdata) ** 2 +
+ (triangulation.y[indices] - ydata) ** 2)
+ return indices[numpy.flip(numpy.argsort(dists), axis=0)]
- else: # it's a curve, item have no picker for now
- if not isinstance(event.artist, (PathCollection, Line2D)):
- _logger.info('Unsupported artist, ignored')
- return
-
- self._picked.append({'kind': 'curve', 'legend': label,
- 'indices': event.ind})
-
- def pickItems(self, x, y, kinds):
- self._picked = []
-
- # Weird way to do an explicit picking: Simulate a button press event
- mouseEvent = MouseEvent('button_press_event',
- self, x, self._mplQtYAxisCoordConversion(y))
- cid = self.mpl_connect('pick_event', self._onPick)
- self.fig.pick(mouseEvent)
- self.mpl_disconnect(cid)
-
- picked = [p for p in self._picked if p['kind'] in kinds]
- self._picked = None
-
- return picked
+ else: # Returns indices if any
+ return info.get('ind', ())
# replot control
@@ -1177,22 +1329,10 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib):
self.ax.get_xbound(), self.ax.get_ybound(), self.ax2.get_ybound())
FigureCanvasQTAgg.resizeEvent(self, event)
- if self.isKeepDataAspectRatio() or self._overlays or self._graphCursor:
+ if self.isKeepDataAspectRatio() or self._hasOverlays():
# This is needed with matplotlib 1.5.x and 2.0.x
self._plot._setDirtyPlot()
- def _drawOverlays(self):
- """Draw overlays if any."""
- if self._overlays or self._graphCursor:
- # There is some overlays or crosshair
-
- # This assume that items are only on left/bottom Axes
- for item in self._overlays:
- self.ax.draw_artist(item)
-
- for item in self._graphCursor:
- self.ax.draw_artist(item)
-
def draw(self):
"""Overload draw
@@ -1201,6 +1341,8 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib):
This is directly called by matplotlib for widget resize.
"""
+ self.updateZOrder()
+
# Starting with mpl 2.1.0, toggling autoscale raises a ValueError
# in some situations. See #1081, #1136, #1163,
if self._matplotlibVersion >= _parse_version("2.0.0"):
@@ -1213,7 +1355,7 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib):
else:
FigureCanvasQTAgg.draw(self)
- if self._overlays or self._graphCursor:
+ if self._hasOverlays():
# Save background
self._background = self.copy_from_bbox(self.fig.bbox)
else:
@@ -1257,7 +1399,7 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib):
if (_parse_version('1.5') <= self._matplotlibVersion < _parse_version('2.1') and
not hasattr(self, '_firstReplot')):
self._firstReplot = False
- if self._overlays or self._graphCursor:
+ if self._hasOverlays():
qt.QTimer.singleShot(0, self.draw) # Request async draw
# cursor
@@ -1276,9 +1418,3 @@ class BackendMatplotlibQt(FigureCanvasQTAgg, BackendMatplotlib):
else:
cursor = self._QT_CURSORS[cursor]
FigureCanvasQTAgg.setCursor(self, qt.QCursor(cursor))
-
- def setBackgroundColors(self, backgroundColor, dataBackgroundColor):
- self._synchronizeBackgroundColors()
-
- def setForegroundColors(self, foregroundColor, gridColor):
- self._synchronizeForegroundColors()
diff --git a/silx/gui/plot/backends/BackendOpenGL.py b/silx/gui/plot/backends/BackendOpenGL.py
index 0420aa9..27f3894 100644..100755
--- a/silx/gui/plot/backends/BackendOpenGL.py
+++ b/silx/gui/plot/backends/BackendOpenGL.py
@@ -30,13 +30,13 @@ __authors__ = ["T. Vincent"]
__license__ = "MIT"
__date__ = "21/12/2018"
-from collections import OrderedDict, namedtuple
import logging
import warnings
import weakref
import numpy
+from .. import items
from .._utils import FLOAT32_MINPOS
from . import BackendBase
from ... import colors
@@ -59,186 +59,66 @@ _logger = logging.getLogger(__name__)
# TODO check if OpenGL is available
# TODO make an off-screen mesa backend
-# Bounds ######################################################################
-
-class Range(namedtuple('Range', ('min_', 'max_'))):
- """Describes a 1D range"""
-
- @property
- def range_(self):
- return self.max_ - self.min_
-
- @property
- def center(self):
- return 0.5 * (self.min_ + self.max_)
-
-
-class Bounds(object):
- """Describes plot bounds with 2 y axis"""
-
- def __init__(self, xMin, xMax, yMin, yMax, y2Min, y2Max):
- self._xAxis = Range(xMin, xMax)
- self._yAxis = Range(yMin, yMax)
- self._y2Axis = Range(y2Min, y2Max)
-
- def __repr__(self):
- return "x: %s, y: %s, y2: %s" % (repr(self._xAxis),
- repr(self._yAxis),
- repr(self._y2Axis))
-
- @property
- def xAxis(self):
- return self._xAxis
-
- @property
- def yAxis(self):
- return self._yAxis
-
- @property
- def y2Axis(self):
- return self._y2Axis
-
-
# Content #####################################################################
-class PlotDataContent(object):
- """Manage plot data content: images and curves.
-
- This class is only meant to work with _OpenGLPlotCanvas.
- """
-
- _PRIMITIVE_TYPES = 'curve', 'image', 'triangles'
-
- def __init__(self):
- self._primitives = OrderedDict() # For images and curves
-
- def add(self, primitive):
- """Add a curve or image to the content dictionary.
-
- This function generates the key in the dict from the primitive.
-
- :param primitive: The primitive to add.
- :type primitive: Instance of GLPlotCurve2D, GLPlotColormap,
- GLPlotRGBAImage.
- """
- if isinstance(primitive, GLPlotCurve2D):
- primitiveType = 'curve'
- elif isinstance(primitive, (GLPlotColormap, GLPlotRGBAImage)):
- primitiveType = 'image'
- elif isinstance(primitive, GLPlotTriangles):
- primitiveType = 'triangles'
- else:
- raise RuntimeError('Unsupported object type: %s', primitive)
-
- key = primitiveType, primitive.info['legend']
- self._primitives[key] = primitive
+class _ShapeItem(dict):
+ def __init__(self, x, y, shape, color, fill, overlay, z,
+ linestyle, linewidth, linebgcolor):
+ super(_ShapeItem, self).__init__()
- def get(self, primitiveType, legend):
- """Get the corresponding primitive of given type with given legend.
+ if shape not in ('polygon', 'rectangle', 'line',
+ 'vline', 'hline', 'polylines'):
+ raise NotImplementedError("Unsupported shape {0}".format(shape))
- :param str primitiveType: Type of primitive ('curve' or 'image').
- :param str legend: The legend of the primitive to retrieve.
- :return: The corresponding curve or None if no such curve.
- """
- assert primitiveType in self._PRIMITIVE_TYPES
- return self._primitives.get((primitiveType, legend))
+ x = numpy.array(x, copy=False)
+ y = numpy.array(y, copy=False)
- def pop(self, primitiveType, key):
- """Pop the corresponding curve or return None if no such curve.
+ if shape == 'rectangle':
+ xMin, xMax = x
+ x = numpy.array((xMin, xMin, xMax, xMax))
+ yMin, yMax = y
+ y = numpy.array((yMin, yMax, yMax, yMin))
- :param str primitiveType:
- :param str key:
- :return:
- """
- assert primitiveType in self._PRIMITIVE_TYPES
- return self._primitives.pop((primitiveType, key), None)
+ # Ignore fill for polylines to mimic matplotlib
+ fill = fill if shape != 'polylines' else False
- def zOrderedPrimitives(self, reverse=False):
- """List of primitives sorted according to their z order.
+ self.update({
+ 'shape': shape,
+ 'color': colors.rgba(color),
+ 'fill': 'hatch' if fill else None,
+ 'x': x,
+ 'y': y,
+ 'linestyle': linestyle,
+ 'linewidth': linewidth,
+ 'linebgcolor': linebgcolor,
+ })
- It is a stable sort (as sorted):
- Original order is preserved when key is the same.
- :param bool reverse: Ascending (True, default) or descending (False).
- """
- return sorted(self._primitives.values(),
- key=lambda primitive: primitive.info['zOrder'],
- reverse=reverse)
-
- def primitives(self):
- """Iterator over all primitives."""
- return self._primitives.values()
-
- def primitiveKeys(self, primitiveType):
- """Iterator over primitives of a specific type."""
- assert primitiveType in self._PRIMITIVE_TYPES
- for type_, key in self._primitives.keys():
- if type_ == primitiveType:
- yield key
-
- def getBounds(self, xPositive=False, yPositive=False):
- """Bounds of the data.
-
- Can return strictly positive bounds (for log scale).
- In this case, curves are clipped to their smaller positive value
- and images with negative min are ignored.
-
- :param bool xPositive: True to get strictly positive range.
- :param bool yPositive: True to get strictly positive range.
- :return: The range of data for x, y and y2, or default (1., 100.)
- if no range found for one dimension.
- :rtype: Bounds
- """
- xMin, yMin, y2Min = float('inf'), float('inf'), float('inf')
- xMax = 0. if xPositive else -float('inf')
- if yPositive:
- yMax, y2Max = 0., 0.
- else:
- yMax, y2Max = -float('inf'), -float('inf')
-
- for item in self._primitives.values():
- # To support curve <= 0. and log and bypass images:
- # If positive only, uses x|yMinPos if available
- # and bypass other data with negative min bounds
- if xPositive:
- itemXMin = getattr(item, 'xMinPos', item.xMin)
- if itemXMin is None or itemXMin < FLOAT32_MINPOS:
- continue
- else:
- itemXMin = item.xMin
+class _MarkerItem(dict):
+ def __init__(self, x, y, text, color,
+ symbol, linestyle, linewidth, constraint, yaxis):
+ super(_MarkerItem, self).__init__()
- if yPositive:
- itemYMin = getattr(item, 'yMinPos', item.yMin)
- if itemYMin is None or itemYMin < FLOAT32_MINPOS:
- continue
- else:
- itemYMin = item.yMin
-
- if itemXMin < xMin:
- xMin = itemXMin
- if item.xMax > xMax:
- xMax = item.xMax
-
- if item.info.get('yAxis') == 'right':
- if itemYMin < y2Min:
- y2Min = itemYMin
- if item.yMax > y2Max:
- y2Max = item.yMax
- else:
- if itemYMin < yMin:
- yMin = itemYMin
- if item.yMax > yMax:
- yMax = item.yMax
+ if symbol is None:
+ symbol = '+'
- # One of the limit has not been updated, return default range
- if xMin >= xMax:
- xMin, xMax = 1., 100.
- if yMin >= yMax:
- yMin, yMax = 1., 100.
- if y2Min >= y2Max:
- y2Min, y2Max = 1., 100.
+ # Apply constraint to provided position
+ isConstraint = (constraint is not None and
+ x is not None and y is not None)
+ if isConstraint:
+ x, y = constraint(x, y)
- return Bounds(xMin, xMax, yMin, yMax, y2Min, y2Max)
+ self.update({
+ 'x': x,
+ 'y': y,
+ 'text': text,
+ 'color': colors.rgba(color),
+ 'constraint': constraint if isConstraint else None,
+ 'symbol': symbol,
+ 'linestyle': linestyle,
+ 'linewidth': linewidth,
+ 'yaxis': yaxis,
+ })
# shaders #####################################################################
@@ -350,9 +230,6 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
self._crosshairCursor = None
self._mousePosInPixels = None
- self._markers = OrderedDict()
- self._items = OrderedDict()
- self._plotContent = PlotDataContent() # For images and curves
self._glGarbageCollector = []
self._plotFrame = GLPlotFrame2D(
@@ -457,7 +334,6 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
def _paintDirectGL(self):
self._renderPlotAreaGL()
self._plotFrame.render()
- self._renderMarkersGL()
self._renderOverlayGL()
def _paintFBOGL(self):
@@ -522,7 +398,6 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
with plotFBOTex.texture:
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, len(self._plotVertices[0]))
- self._renderMarkersGL()
self._renderOverlayGL()
def paintGL(self):
@@ -543,120 +418,203 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
# self._paintDirectGL()
self._paintFBOGL()
- def _renderMarkersGL(self):
- if len(self._markers) == 0:
- return
+ def _renderItems(self, overlay=False):
+ """Render items according to :class:`PlotWidget` order
- plotWidth, plotHeight = self.getPlotBoundsInPixels()[2:]
+ Note: Scissor test should already be set.
- # Render in plot area
- gl.glScissor(self._plotFrame.margins.left,
- self._plotFrame.margins.bottom,
- plotWidth, plotHeight)
- gl.glEnable(gl.GL_SCISSOR_TEST)
-
- gl.glViewport(0, 0, self._plotFrame.size[0], self._plotFrame.size[1])
+ :param bool overlay:
+ False (the default) to render item that are not overlays.
+ True to render items that are overlays.
+ """
+ # Values that are often used
+ plotWidth, plotHeight = self.getPlotBoundsInPixels()[2:]
+ isXLog = self._plotFrame.xAxis.isLog
+ isYLog = self._plotFrame.yAxis.isLog
+ # Used by marker rendering
labels = []
pixelOffset = 3
- for marker in self._markers.values():
- xCoord, yCoord = marker['x'], marker['y']
-
- if ((self._plotFrame.xAxis.isLog and
- xCoord is not None and
- xCoord <= 0) or
- (self._plotFrame.yAxis.isLog and
- yCoord is not None and
- yCoord <= 0)):
- # Do not render markers with negative coords on log axis
+ for plotItem in self.getItemsFromBackToFront(
+ condition=lambda i: i.isVisible() and i.isOverlay() == overlay):
+ if plotItem._backendRenderer is None:
continue
- if xCoord is None or yCoord is None:
- pixelPos = self.dataToPixel(
- xCoord, yCoord, axis='left', check=False)
-
- if xCoord is None: # Horizontal line in data space
- if marker['text'] is not None:
- x = self._plotFrame.size[0] - \
- self._plotFrame.margins.right - pixelOffset
- y = pixelPos[1] - pixelOffset
- label = Text2D(marker['text'], x, y,
- color=marker['color'],
- bgColor=(1., 1., 1., 0.5),
- align=RIGHT, valign=BOTTOM)
- labels.append(label)
+ item = plotItem._backendRenderer
+
+ if isinstance(item, (GLPlotCurve2D,
+ GLPlotColormap,
+ GLPlotRGBAImage,
+ GLPlotTriangles)): # Render data items
+ gl.glViewport(self._plotFrame.margins.left,
+ self._plotFrame.margins.bottom,
+ plotWidth, plotHeight)
+ if isinstance(item, GLPlotCurve2D) and item.info.get('yAxis') == 'right':
+ item.render(self._plotFrame.transformedDataY2ProjMat,
+ isXLog, isYLog)
+ else:
+ item.render(self._plotFrame.transformedDataProjMat,
+ isXLog, isYLog)
+
+ elif isinstance(item, _ShapeItem): # Render shape items
+ gl.glViewport(0, 0, self._plotFrame.size[0], self._plotFrame.size[1])
+
+ if ((isXLog and numpy.min(item['x']) < FLOAT32_MINPOS) or
+ (isYLog and numpy.min(item['y']) < FLOAT32_MINPOS)):
+ # Ignore items <= 0. on log axes
+ continue
+
+ if item['shape'] == 'hline':
width = self._plotFrame.size[0]
- lines = GLLines2D((0, width), (pixelPos[1], pixelPos[1]),
- style=marker['linestyle'],
- color=marker['color'],
- width=marker['linewidth'])
+ _, yPixel = self._plot.dataToPixel(
+ None, item['y'], axis='left', check=False)
+ points = numpy.array(((0., yPixel), (width, yPixel)),
+ dtype=numpy.float32)
+
+ elif item['shape'] == 'vline':
+ xPixel, _ = self._plot.dataToPixel(
+ item['x'], None, axis='left', check=False)
+ height = self._plotFrame.size[1]
+ points = numpy.array(((xPixel, 0), (xPixel, height)),
+ dtype=numpy.float32)
+
+ else:
+ points = numpy.array([
+ self._plot.dataToPixel(x, y, axis='left', check=False)
+ for (x, y) in zip(item['x'], item['y'])])
+
+ # Draw the fill
+ if (item['fill'] is not None and
+ item['shape'] not in ('hline', 'vline')):
+ self._progBase.use()
+ gl.glUniformMatrix4fv(
+ self._progBase.uniforms['matrix'], 1, gl.GL_TRUE,
+ self.matScreenProj.astype(numpy.float32))
+ gl.glUniform2i(self._progBase.uniforms['isLog'], False, False)
+ gl.glUniform1f(self._progBase.uniforms['tickLen'], 0.)
+
+ shape2D = FilledShape2D(
+ points, style=item['fill'], color=item['color'])
+ shape2D.render(
+ posAttrib=self._progBase.attributes['position'],
+ colorUnif=self._progBase.uniforms['color'],
+ hatchStepUnif=self._progBase.uniforms['hatchStep'])
+
+ # Draw the stroke
+ if item['linestyle'] not in ('', ' ', None):
+ if item['shape'] != 'polylines':
+ # close the polyline
+ points = numpy.append(points,
+ numpy.atleast_2d(points[0]), axis=0)
+
+ lines = GLLines2D(points[:, 0], points[:, 1],
+ style=item['linestyle'],
+ color=item['color'],
+ dash2ndColor=item['linebgcolor'],
+ width=item['linewidth'])
lines.render(self.matScreenProj)
- else: # yCoord is None: vertical line in data space
- if marker['text'] is not None:
+ elif isinstance(item, _MarkerItem):
+ gl.glViewport(0, 0, self._plotFrame.size[0], self._plotFrame.size[1])
+
+ xCoord, yCoord, yAxis = item['x'], item['y'], item['yaxis']
+
+ if ((isXLog and xCoord is not None and xCoord <= 0) or
+ (isYLog and yCoord is not None and yCoord <= 0)):
+ # Do not render markers with negative coords on log axis
+ continue
+
+ if xCoord is None or yCoord is None:
+ pixelPos = self._plot.dataToPixel(
+ xCoord, yCoord, axis=yAxis, check=False)
+
+ if xCoord is None: # Horizontal line in data space
+ if item['text'] is not None:
+ x = self._plotFrame.size[0] - \
+ self._plotFrame.margins.right - pixelOffset
+ y = pixelPos[1] - pixelOffset
+ label = Text2D(item['text'], x, y,
+ color=item['color'],
+ bgColor=(1., 1., 1., 0.5),
+ align=RIGHT, valign=BOTTOM)
+ labels.append(label)
+
+ width = self._plotFrame.size[0]
+ lines = GLLines2D((0, width), (pixelPos[1], pixelPos[1]),
+ style=item['linestyle'],
+ color=item['color'],
+ width=item['linewidth'])
+ lines.render(self.matScreenProj)
+
+ else: # yCoord is None: vertical line in data space
+ if item['text'] is not None:
+ x = pixelPos[0] + pixelOffset
+ y = self._plotFrame.margins.top + pixelOffset
+ label = Text2D(item['text'], x, y,
+ color=item['color'],
+ bgColor=(1., 1., 1., 0.5),
+ align=LEFT, valign=TOP)
+ labels.append(label)
+
+ height = self._plotFrame.size[1]
+ lines = GLLines2D((pixelPos[0], pixelPos[0]), (0, height),
+ style=item['linestyle'],
+ color=item['color'],
+ width=item['linewidth'])
+ lines.render(self.matScreenProj)
+
+ else:
+ pixelPos = self._plot.dataToPixel(
+ xCoord, yCoord, axis=yAxis, check=True)
+ if pixelPos is None:
+ # Do not render markers outside visible plot area
+ continue
+
+ if item['text'] is not None:
x = pixelPos[0] + pixelOffset
- y = self._plotFrame.margins.top + pixelOffset
- label = Text2D(marker['text'], x, y,
- color=marker['color'],
+ y = pixelPos[1] + pixelOffset
+ label = Text2D(item['text'], x, y,
+ color=item['color'],
bgColor=(1., 1., 1., 0.5),
align=LEFT, valign=TOP)
labels.append(label)
- height = self._plotFrame.size[1]
- lines = GLLines2D((pixelPos[0], pixelPos[0]), (0, height),
- style=marker['linestyle'],
- color=marker['color'],
- width=marker['linewidth'])
- lines.render(self.matScreenProj)
+ # For now simple implementation: using a curve for each marker
+ # Should pack all markers to a single set of points
+ markerCurve = GLPlotCurve2D(
+ numpy.array((pixelPos[0],), dtype=numpy.float64),
+ numpy.array((pixelPos[1],), dtype=numpy.float64),
+ marker=item['symbol'],
+ markerColor=item['color'],
+ markerSize=11)
+ markerCurve.render(self.matScreenProj, False, False)
+ markerCurve.discard()
else:
- pixelPos = self.dataToPixel(
- xCoord, yCoord, axis='left', check=True)
- if pixelPos is None:
- # Do not render markers outside visible plot area
- continue
-
- if marker['text'] is not None:
- x = pixelPos[0] + pixelOffset
- y = pixelPos[1] + pixelOffset
- label = Text2D(marker['text'], x, y,
- color=marker['color'],
- bgColor=(1., 1., 1., 0.5),
- align=LEFT, valign=TOP)
- labels.append(label)
-
- # For now simple implementation: using a curve for each marker
- # Should pack all markers to a single set of points
- markerCurve = GLPlotCurve2D(
- numpy.array((pixelPos[0],), dtype=numpy.float64),
- numpy.array((pixelPos[1],), dtype=numpy.float64),
- marker=marker['symbol'],
- markerColor=marker['color'],
- markerSize=11)
- markerCurve.render(self.matScreenProj, False, False)
- markerCurve.discard()
-
- gl.glViewport(0, 0, self._plotFrame.size[0], self._plotFrame.size[1])
+ _logger.error('Unsupported item: %s', str(item))
+ continue
# Render marker labels
+ gl.glViewport(0, 0, self._plotFrame.size[0], self._plotFrame.size[1])
for label in labels:
label.render(self.matScreenProj)
- gl.glDisable(gl.GL_SCISSOR_TEST)
-
def _renderOverlayGL(self):
- # Render crosshair cursor
- if self._crosshairCursor is not None:
- plotWidth, plotHeight = self.getPlotBoundsInPixels()[2:]
+ """Render overlay layer: overlay items and crosshair."""
+ plotWidth, plotHeight = self.getPlotBoundsInPixels()[2:]
+
+ # Scissor to plot area
+ gl.glScissor(self._plotFrame.margins.left,
+ self._plotFrame.margins.bottom,
+ plotWidth, plotHeight)
+ gl.glEnable(gl.GL_SCISSOR_TEST)
- # Scissor to plot area
- gl.glScissor(self._plotFrame.margins.left,
- self._plotFrame.margins.bottom,
- plotWidth, plotHeight)
- gl.glEnable(gl.GL_SCISSOR_TEST)
+ self._renderItems(overlay=True)
+ # Render crosshair cursor
+ if self._crosshairCursor is not None and self._mousePosInPixels is not None:
self._progBase.use()
gl.glUniform2i(self._progBase.uniforms['isLog'], False, False)
gl.glUniform1f(self._progBase.uniforms['tickLen'], 0.)
@@ -665,39 +623,39 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
colorUnif = self._progBase.uniforms['color']
hatchStepUnif = self._progBase.uniforms['hatchStep']
- # Render crosshair cursor in screen frame but with scissor
- if (self._crosshairCursor is not None and
- self._mousePosInPixels is not None):
- gl.glViewport(
- 0, 0, self._plotFrame.size[0], self._plotFrame.size[1])
-
- gl.glUniformMatrix4fv(matrixUnif, 1, gl.GL_TRUE,
- self.matScreenProj.astype(numpy.float32))
-
- color, lineWidth = self._crosshairCursor
- gl.glUniform4f(colorUnif, *color)
- gl.glUniform1i(hatchStepUnif, 0)
-
- xPixel, yPixel = self._mousePosInPixels
- xPixel, yPixel = xPixel + 0.5, yPixel + 0.5
- vertices = numpy.array(((0., yPixel),
- (self._plotFrame.size[0], yPixel),
- (xPixel, 0.),
- (xPixel, self._plotFrame.size[1])),
- dtype=numpy.float32)
-
- gl.glEnableVertexAttribArray(posAttrib)
- gl.glVertexAttribPointer(posAttrib,
- 2,
- gl.GL_FLOAT,
- gl.GL_FALSE,
- 0, vertices)
- gl.glLineWidth(lineWidth)
- gl.glDrawArrays(gl.GL_LINES, 0, len(vertices))
-
- gl.glDisable(gl.GL_SCISSOR_TEST)
+ gl.glViewport(0, 0, self._plotFrame.size[0], self._plotFrame.size[1])
+
+ gl.glUniformMatrix4fv(matrixUnif, 1, gl.GL_TRUE,
+ self.matScreenProj.astype(numpy.float32))
+
+ color, lineWidth = self._crosshairCursor
+ gl.glUniform4f(colorUnif, *color)
+ gl.glUniform1i(hatchStepUnif, 0)
+
+ xPixel, yPixel = self._mousePosInPixels
+ xPixel, yPixel = xPixel + 0.5, yPixel + 0.5
+ vertices = numpy.array(((0., yPixel),
+ (self._plotFrame.size[0], yPixel),
+ (xPixel, 0.),
+ (xPixel, self._plotFrame.size[1])),
+ dtype=numpy.float32)
+
+ gl.glEnableVertexAttribArray(posAttrib)
+ gl.glVertexAttribPointer(posAttrib,
+ 2,
+ gl.GL_FLOAT,
+ gl.GL_FALSE,
+ 0, vertices)
+ gl.glLineWidth(lineWidth)
+ gl.glDrawArrays(gl.GL_LINES, 0, len(vertices))
+
+ gl.glDisable(gl.GL_SCISSOR_TEST)
def _renderPlotAreaGL(self):
+ """Render base layer of plot area.
+
+ It renders the background, grid and items except overlays
+ """
plotWidth, plotHeight = self.getPlotBoundsInPixels()[2:]
gl.glScissor(self._plotFrame.margins.left,
@@ -713,85 +671,9 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
# Matrix
trBounds = self._plotFrame.transformedDataRanges
- if trBounds.x[0] == trBounds.x[1] or \
- trBounds.y[0] == trBounds.y[1]:
- return
-
- isXLog = self._plotFrame.xAxis.isLog
- isYLog = self._plotFrame.yAxis.isLog
-
- gl.glViewport(self._plotFrame.margins.left,
- self._plotFrame.margins.bottom,
- plotWidth, plotHeight)
-
- # Render images and curves
- # sorted is stable: original order is preserved when key is the same
- for item in self._plotContent.zOrderedPrimitives():
- if item.info.get('yAxis') == 'right':
- item.render(self._plotFrame.transformedDataY2ProjMat,
- isXLog, isYLog)
- else:
- item.render(self._plotFrame.transformedDataProjMat,
- isXLog, isYLog)
-
- # Render Items
- gl.glViewport(0, 0, self._plotFrame.size[0], self._plotFrame.size[1])
-
- for item in self._items.values():
- if ((isXLog and numpy.min(item['x']) < FLOAT32_MINPOS) or
- (isYLog and numpy.min(item['y']) < FLOAT32_MINPOS)):
- # Ignore items <= 0. on log axes
- continue
-
- if item['shape'] == 'hline':
- width = self._plotFrame.size[0]
- _, yPixel = self.dataToPixel(
- None, item['y'], axis='left', check=False)
- points = numpy.array(((0., yPixel), (width, yPixel)),
- dtype=numpy.float32)
-
- elif item['shape'] == 'vline':
- xPixel, _ = self.dataToPixel(
- item['x'], None, axis='left', check=False)
- height = self._plotFrame.size[1]
- points = numpy.array(((xPixel, 0), (xPixel, height)),
- dtype=numpy.float32)
-
- else:
- points = numpy.array([
- self.dataToPixel(x, y, axis='left', check=False)
- for (x, y) in zip(item['x'], item['y'])])
-
- # Draw the fill
- if (item['fill'] is not None and
- item['shape'] not in ('hline', 'vline')):
- self._progBase.use()
- gl.glUniformMatrix4fv(
- self._progBase.uniforms['matrix'], 1, gl.GL_TRUE,
- self.matScreenProj.astype(numpy.float32))
- gl.glUniform2i(self._progBase.uniforms['isLog'], False, False)
- gl.glUniform1f(self._progBase.uniforms['tickLen'], 0.)
-
- shape2D = FilledShape2D(
- points, style=item['fill'], color=item['color'])
- shape2D.render(
- posAttrib=self._progBase.attributes['position'],
- colorUnif=self._progBase.uniforms['color'],
- hatchStepUnif=self._progBase.uniforms['hatchStep'])
-
- # Draw the stroke
- if item['linestyle'] not in ('', ' ', None):
- if item['shape'] != 'polylines':
- # close the polyline
- points = numpy.append(points,
- numpy.atleast_2d(points[0]), axis=0)
-
- lines = GLLines2D(points[:, 0], points[:, 1],
- style=item['linestyle'],
- color=item['color'],
- dash2ndColor=item['linebgcolor'],
- width=item['linewidth'])
- lines.render(self.matScreenProj)
+ if trBounds.x[0] != trBounds.x[1] and trBounds.y[0] != trBounds.y[1]:
+ # Do rendering of items
+ self._renderItems(overlay=False)
gl.glDisable(gl.GL_SCISSOR_TEST)
@@ -841,13 +723,13 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
else:
raise ValueError('Unsupported data type')
- def addCurve(self, x, y, legend,
+ def addCurve(self, x, y,
color, symbol, linewidth, linestyle,
yaxis,
- xerror, yerror, z, selectable,
- fill, alpha, symbolsize):
- for parameter in (x, y, legend, color, symbol, linewidth, linestyle,
- yaxis, z, selectable, fill, symbolsize):
+ xerror, yerror, z,
+ fill, alpha, symbolsize, baseline):
+ for parameter in (x, y, color, symbol, linewidth, linestyle,
+ yaxis, z, fill, symbolsize):
assert parameter is not None
assert yaxis in ('left', 'right')
@@ -939,10 +821,9 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
if color is not None:
color = color[0], color[1], color[2], color[3] * alpha
- behaviors = set()
- if selectable:
- behaviors.add('selectable')
-
+ fillColor = None
+ if fill is True:
+ fillColor = color
curve = GLPlotCurve2D(x, y, colorArray,
xError=xerror,
yError=yerror,
@@ -952,36 +833,24 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
marker=symbol,
markerColor=color,
markerSize=symbolsize,
- fillColor=color if fill else None,
+ fillColor=fillColor,
+ baseline=baseline,
isYLog=isYLog)
curve.info = {
- 'legend': legend,
- 'zOrder': z,
- 'behaviors': behaviors,
'yAxis': 'left' if yaxis is None else yaxis,
}
if yaxis == "right":
self._plotFrame.isY2Axis = True
- self._plotContent.add(curve)
+ return curve
- return legend, 'curve'
-
- def addImage(self, data, legend,
+ def addImage(self, data,
origin, scale, z,
- selectable, draggable,
colormap, alpha):
- for parameter in (data, legend, origin, scale, z,
- selectable, draggable):
+ for parameter in (data, origin, scale, z):
assert parameter is not None
- behaviors = set()
- if selectable:
- behaviors.add('selectable')
- if draggable:
- behaviors.add('draggable')
-
if data.ndim == 2:
# Ensure array is contiguous and eventually convert its type
if data.dtype in (numpy.float32, numpy.uint8, numpy.uint16):
@@ -1002,12 +871,6 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
colormapIsLog,
cmapRange,
alpha)
- image.info = {
- 'legend': legend,
- 'zOrder': z,
- 'behaviors': behaviors
- }
- self._plotContent.add(image)
elif len(data.shape) == 3:
# For RGB, RGBA data
@@ -1022,29 +885,21 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
image = GLPlotRGBAImage(data, origin, scale, alpha)
- image.info = {
- 'legend': legend,
- 'zOrder': z,
- 'behaviors': behaviors
- }
-
- if self._plotFrame.xAxis.isLog and image.xMin <= 0.:
- raise RuntimeError(
- 'Cannot add image with X <= 0 with X axis log scale')
- if self._plotFrame.yAxis.isLog and image.yMin <= 0.:
- raise RuntimeError(
- 'Cannot add image with Y <= 0 with Y axis log scale')
-
- self._plotContent.add(image)
-
else:
raise RuntimeError("Unsupported data shape {0}".format(data.shape))
- return legend, 'image'
+ # TODO is this needed?
+ if self._plotFrame.xAxis.isLog and image.xMin <= 0.:
+ raise RuntimeError(
+ 'Cannot add image with X <= 0 with X axis log scale')
+ if self._plotFrame.yAxis.isLog and image.yMin <= 0.:
+ raise RuntimeError(
+ 'Cannot add image with Y <= 0 with Y axis log scale')
- def addTriangles(self, x, y, triangles, legend,
- color, z, selectable, alpha):
+ return image
+ def addTriangles(self, x, y, triangles,
+ color, z, alpha):
# Handle axes log scale: convert data
if self._plotFrame.xAxis.isLog:
x = numpy.log10(x)
@@ -1052,31 +907,14 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
y = numpy.log10(y)
triangles = GLPlotTriangles(x, y, color, triangles, alpha)
- triangles.info = {
- 'legend': legend,
- 'zOrder': z,
- 'behaviors': set(['selectable']) if selectable else set(),
- }
- self._plotContent.add(triangles)
- return legend, 'triangles'
+ return triangles
- def addItem(self, x, y, legend, shape, color, fill, overlay, z,
+ def addItem(self, x, y, shape, color, fill, overlay, z,
linestyle, linewidth, linebgcolor):
- # TODO handle overlay
- if shape not in ('polygon', 'rectangle', 'line',
- 'vline', 'hline', 'polylines'):
- raise NotImplementedError("Unsupported shape {0}".format(shape))
-
x = numpy.array(x, copy=False)
y = numpy.array(y, copy=False)
- if shape == 'rectangle':
- xMin, xMax = x
- x = numpy.array((xMin, xMin, xMax, xMax))
- yMin, yMax = y
- y = numpy.array((yMin, yMax, yMax, yMin))
-
# TODO is this needed?
if self._plotFrame.xAxis.isLog and x.min() <= 0.:
raise RuntimeError(
@@ -1085,84 +923,35 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
raise RuntimeError(
'Cannot add item with Y <= 0 with Y axis log scale')
- # Ignore fill for polylines to mimic matplotlib
- fill = fill if shape != 'polylines' else False
-
- self._items[legend] = {
- 'shape': shape,
- 'color': colors.rgba(color),
- 'fill': 'hatch' if fill else None,
- 'x': x,
- 'y': y,
- 'linestyle': linestyle,
- 'linewidth': linewidth,
- 'linebgcolor': linebgcolor,
- }
-
- return legend, 'item'
-
- def addMarker(self, x, y, legend, text, color,
- selectable, draggable,
- symbol, linestyle, linewidth, constraint):
-
- if symbol is None:
- symbol = '+'
-
- behaviors = set()
- if selectable:
- behaviors.add('selectable')
- if draggable:
- behaviors.add('draggable')
-
- # Apply constraint to provided position
- isConstraint = (draggable and constraint is not None and
- x is not None and y is not None)
- if isConstraint:
- x, y = constraint(x, y)
-
- self._markers[legend] = {
- 'x': x,
- 'y': y,
- 'legend': legend,
- 'text': text,
- 'color': colors.rgba(color),
- 'behaviors': behaviors,
- 'constraint': constraint if isConstraint else None,
- 'symbol': symbol,
- 'linestyle': linestyle,
- 'linewidth': linewidth,
- }
+ return _ShapeItem(x, y, shape, color, fill, overlay, z,
+ linestyle, linewidth, linebgcolor)
- return legend, 'marker'
+ def addMarker(self, x, y, text, color,
+ symbol, linestyle, linewidth, constraint, yaxis):
+ return _MarkerItem(x, y, text, color,
+ symbol, linestyle, linewidth, constraint, yaxis)
# Remove methods
def remove(self, item):
- legend, kind = item
-
- if kind == 'curve':
- curve = self._plotContent.pop('curve', legend)
- if curve is not None:
+ if isinstance(item, (GLPlotCurve2D,
+ GLPlotColormap,
+ GLPlotRGBAImage,
+ GLPlotTriangles)):
+ if isinstance(item, GLPlotCurve2D):
# Check if some curves remains on the right Y axis
- y2AxisItems = (item for item in self._plotContent.primitives()
- if item.info.get('yAxis', 'left') == 'right')
+ y2AxisItems = (item for item in self._plot.getItems()
+ if isinstance(item, items.YAxisMixIn) and
+ item.getYAxis() == 'right')
self._plotFrame.isY2Axis = next(y2AxisItems, None) is not None
- self._glGarbageCollector.append(curve)
-
- elif kind in ('image', 'triangles'):
- item = self._plotContent.pop(kind, legend)
- if item is not None:
- self._glGarbageCollector.append(item)
+ self._glGarbageCollector.append(item)
- elif kind == 'marker':
- self._markers.pop(legend, False)
-
- elif kind == 'item':
- self._items.pop(legend, False)
+ elif isinstance(item, (_MarkerItem, _ShapeItem)):
+ pass # No-op
else:
- _logger.error('Unsupported kind: %s', str(kind))
+ _logger.error('Unsupported item: %s', str(item))
# Interaction methods
@@ -1212,8 +1001,8 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
:param GLPlotCurve2D item:
:param float x: X position of the mouse in widget coordinates
:param float y: Y position of the mouse in widget coordinates
- :return: List of indices of picked points
- :rtype: List[int]
+ :return: List of indices of picked points or None if not picked
+ :rtype: Union[List[int],None]
"""
offset = self._PICK_OFFSET
if item.marker is not None:
@@ -1224,17 +1013,17 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
yAxis = item.info['yAxis']
inAreaPos = self._mouseInPlotArea(x - offset, y - offset)
- dataPos = self.pixelToData(inAreaPos[0], inAreaPos[1],
- axis=yAxis, check=True)
+ dataPos = self._plot.pixelToData(inAreaPos[0], inAreaPos[1],
+ axis=yAxis, check=True)
if dataPos is None:
- return []
+ return None
xPick0, yPick0 = dataPos
inAreaPos = self._mouseInPlotArea(x + offset, y + offset)
- dataPos = self.pixelToData(inAreaPos[0], inAreaPos[1],
- axis=yAxis, check=True)
+ dataPos = self._plot.pixelToData(inAreaPos[0], inAreaPos[1],
+ axis=yAxis, check=True)
if dataPos is None:
- return []
+ return None
xPick1, yPick1 = dataPos
if xPick0 < xPick1:
@@ -1260,69 +1049,58 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
return item.pick(xPickMin, yPickMin,
xPickMax, yPickMax)
- def pickItems(self, x, y, kinds):
- picked = []
-
- dataPos = self.pixelToData(x, y, axis='left', check=True)
- if dataPos is not None:
- # Pick markers
- if 'marker' in kinds:
- for marker in reversed(list(self._markers.values())):
- pixelPos = self.dataToPixel(
- marker['x'], marker['y'], axis='left', check=False)
- if pixelPos is None: # negative coord on a log axis
- continue
+ def pickItem(self, x, y, item):
+ dataPos = self._plot.pixelToData(x, y, axis='left', check=True)
+ if dataPos is None:
+ return None # Outside plot area
+
+ if item is None:
+ _logger.error("No item provided for picking")
+ return None
+
+ # Pick markers
+ if isinstance(item, _MarkerItem):
+ yaxis = item['yaxis']
+ pixelPos = self._plot.dataToPixel(
+ item['x'], item['y'], axis=yaxis, check=False)
+ if pixelPos is None:
+ return None # negative coord on a log axis
+
+ if item['x'] is None: # Horizontal line
+ pt1 = self._plot.pixelToData(
+ x, y - self._PICK_OFFSET, axis=yaxis, check=False)
+ pt2 = self._plot.pixelToData(
+ x, y + self._PICK_OFFSET, axis=yaxis, check=False)
+ isPicked = (min(pt1[1], pt2[1]) <= item['y'] <=
+ max(pt1[1], pt2[1]))
+
+ elif item['y'] is None: # Vertical line
+ pt1 = self._plot.pixelToData(
+ x - self._PICK_OFFSET, y, axis=yaxis, check=False)
+ pt2 = self._plot.pixelToData(
+ x + self._PICK_OFFSET, y, axis=yaxis, check=False)
+ isPicked = (min(pt1[0], pt2[0]) <= item['x'] <=
+ max(pt1[0], pt2[0]))
- if marker['x'] is None: # Horizontal line
- pt1 = self.pixelToData(
- x, y - self._PICK_OFFSET, axis='left', check=False)
- pt2 = self.pixelToData(
- x, y + self._PICK_OFFSET, axis='left', check=False)
- isPicked = (min(pt1[1], pt2[1]) <= marker['y'] <=
- max(pt1[1], pt2[1]))
-
- elif marker['y'] is None: # Vertical line
- pt1 = self.pixelToData(
- x - self._PICK_OFFSET, y, axis='left', check=False)
- pt2 = self.pixelToData(
- x + self._PICK_OFFSET, y, axis='left', check=False)
- isPicked = (min(pt1[0], pt2[0]) <= marker['x'] <=
- max(pt1[0], pt2[0]))
-
- else:
- isPicked = (
- numpy.fabs(x - pixelPos[0]) <= self._PICK_OFFSET and
- numpy.fabs(y - pixelPos[1]) <= self._PICK_OFFSET)
-
- if isPicked:
- picked.append(dict(kind='marker',
- legend=marker['legend']))
-
- # Pick image and curves
- if 'image' in kinds or 'curve' in kinds:
- for item in self._plotContent.zOrderedPrimitives(reverse=True):
- if ('image' in kinds and
- isinstance(item, (GLPlotColormap, GLPlotRGBAImage))):
- pickedPos = item.pick(*dataPos)
- if pickedPos is not None:
- picked.append(dict(kind='image',
- legend=item.info['legend']))
-
- elif 'curve' in kinds:
- if isinstance(item, GLPlotCurve2D):
- pickedIndices = self.__pickCurves(item, x, y)
- if pickedIndices:
- picked.append(dict(kind='curve',
- legend=item.info['legend'],
- indices=pickedIndices))
-
- elif isinstance(item, GLPlotTriangles):
- pickedIndices = item.pick(*dataPos)
- if pickedIndices:
- picked.append(dict(kind='curve',
- legend=item.info['legend'],
- indices=pickedIndices))
- return picked
+ else:
+ isPicked = (
+ numpy.fabs(x - pixelPos[0]) <= self._PICK_OFFSET and
+ numpy.fabs(y - pixelPos[1]) <= self._PICK_OFFSET)
+
+ return (0,) if isPicked else None
+
+ # Pick image, curve, triangles
+ elif isinstance(item, (GLPlotCurve2D,
+ GLPlotColormap,
+ GLPlotRGBAImage,
+ GLPlotTriangles)):
+ if isinstance(item, (GLPlotColormap, GLPlotRGBAImage, GLPlotTriangles)):
+ return item.pick(*dataPos) # Might be None
+
+ elif isinstance(item, GLPlotCurve2D):
+ return self.__pickCurves(item, x, y)
+ else:
+ return None
# Update curve
@@ -1426,12 +1204,11 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
return
if keepDim is None:
- dataBounds = self._plotContent.getBounds(
- self._plotFrame.xAxis.isLog, self._plotFrame.yAxis.isLog)
- if dataBounds.yAxis.range_ != 0.:
- dataRatio = dataBounds.xAxis.range_
- dataRatio /= float(dataBounds.yAxis.range_)
-
+ ranges = self._plot.getDataRange()
+ if (ranges.y is not None and
+ ranges.x is not None and
+ (ranges.y[1] - ranges.y[0]) != 0.):
+ dataRatio = (ranges.x[1] - ranges.x[0]) / float(ranges.y[1] - ranges.y[0])
plotRatio = plotWidth / float(plotHeight) # Test != 0 before
keepDim = 'x' if dataRatio > plotRatio else 'y'
@@ -1564,51 +1341,10 @@ class BackendOpenGL(BackendBase.BackendBase, glu.OpenGLWidget):
# Data <-> Pixel coordinates conversion
- def dataToPixel(self, x, y, axis, check=False):
- assert axis in ('left', 'right')
-
- if x is None or y is None:
- dataBounds = self._plotContent.getBounds(
- self._plotFrame.xAxis.isLog, self._plotFrame.yAxis.isLog)
-
- if x is None:
- x = dataBounds.xAxis.center
-
- if y is None:
- if axis == 'left':
- y = dataBounds.yAxis.center
- else:
- y = dataBounds.y2Axis.center
-
- result = self._plotFrame.dataToPixel(x, y, axis)
-
- if check and result is not None:
- xPixel, yPixel = result
- width, height = self._plotFrame.size
- if (xPixel < self._plotFrame.margins.left or
- xPixel > (width - self._plotFrame.margins.right) or
- yPixel < self._plotFrame.margins.top or
- yPixel > height - self._plotFrame.margins.bottom):
- return None # (x, y) is out of plot area
-
- return result
-
- def pixelToData(self, x, y, axis, check):
- assert axis in ("left", "right")
-
- if x is None:
- x = self._plotFrame.size[0] / 2.
- if y is None:
- y = self._plotFrame.size[1] / 2.
-
- if check and (x < self._plotFrame.margins.left or
- x > (self._plotFrame.size[0] -
- self._plotFrame.margins.right) or
- y < self._plotFrame.margins.top or
- y > (self._plotFrame.size[1] -
- self._plotFrame.margins.bottom)):
- return None # (x, y) is out of plot area
+ def dataToPixel(self, x, y, axis):
+ return self._plotFrame.dataToPixel(x, y, axis)
+ def pixelToData(self, x, y, axis):
return self._plotFrame.pixelToData(x, y, axis)
def getPlotBoundsInPixels(self):
diff --git a/silx/gui/plot/backends/glutils/GLPlotCurve.py b/silx/gui/plot/backends/glutils/GLPlotCurve.py
index 5f8d652..3a0ebac 100644
--- a/silx/gui/plot/backends/glutils/GLPlotCurve.py
+++ b/silx/gui/plot/backends/glutils/GLPlotCurve.py
@@ -132,8 +132,7 @@ class _Fill2D(object):
self.xData is not None and self.yData is not None):
# Get slices of not NaN values longer than 1 element
- isnan = numpy.logical_or(numpy.isnan(self.xData),
- numpy.isnan(self.yData))
+ isnan = numpy.logical_or(numpy.isnan(self.xData), numpy.isnan(self.yData))
notnan = numpy.logical_not(isnan)
start = numpy.where(numpy.logical_and(isnan[:-1], notnan[1:]))[0] + 1
if notnan[0]:
@@ -147,22 +146,25 @@ class _Fill2D(object):
# Number of points: slice + 2 * leading and trailing points
# Twice leading and trailing points to produce degenerated triangles
- nbPoints = numpy.sum(numpy.diff(slices, axis=1)) + 4 * len(slices)
+ nbPoints = numpy.sum(numpy.diff(slices, axis=1)) * 2 + 4 * len(slices)
points = numpy.empty((nbPoints, 2), dtype=numpy.float32)
offset = 0
+ # invert baseline for filling
+ new_y_data = numpy.append(self.yData, self.baseline)
for start, end in slices:
# Duplicate first point for connecting degenerated triangle
- points[offset:offset+2] = self.xData[start], self.baseline
+ points[offset:offset+2] = self.xData[start], new_y_data[start]
# 2nd point of the polygon is last point
- points[offset+2] = self.xData[end-1], self.baseline
+ points[offset+2] = self.xData[start], self.baseline[start]
- # Add all points from the data
- indices = start + buildFillMaskIndices(end - start)
+ indices = numpy.append(numpy.arange(start, end),
+ numpy.arange(len(self.xData) + end-1, len(self.xData) + start-1, -1))
+ indices = indices[buildFillMaskIndices(len(indices))]
- points[offset+3:offset+3+len(indices), 0] = self.xData[indices]
- points[offset+3:offset+3+len(indices), 1] = self.yData[indices]
+ points[offset+3:offset+3+len(indices), 0] = self.xData[indices % len(self.xData)]
+ points[offset+3:offset+3+len(indices), 1] = new_y_data[indices]
# Duplicate last point for connecting degenerated triangle
points[offset+3+len(indices)] = points[offset+3+len(indices)-1]
@@ -526,7 +528,16 @@ def distancesFromArrays(xData, yData):
DIAMOND, CIRCLE, SQUARE, PLUS, X_MARKER, POINT, PIXEL, ASTERISK = \
'd', 'o', 's', '+', 'x', '.', ',', '*'
-H_LINE, V_LINE = '_', '|'
+H_LINE, V_LINE, HEART = '_', '|', u'\u2665'
+
+TICK_LEFT = "tickleft"
+TICK_RIGHT = "tickright"
+TICK_UP = "tickup"
+TICK_DOWN = "tickdown"
+CARET_LEFT = "caretleft"
+CARET_RIGHT = "caretright"
+CARET_UP = "caretup"
+CARET_DOWN = "caretdown"
class _Points2D(object):
@@ -542,7 +553,8 @@ class _Points2D(object):
"""
MARKERS = (DIAMOND, CIRCLE, SQUARE, PLUS, X_MARKER, POINT, PIXEL, ASTERISK,
- H_LINE, V_LINE)
+ H_LINE, V_LINE, HEART, TICK_LEFT, TICK_RIGHT, TICK_UP, TICK_DOWN,
+ CARET_LEFT, CARET_RIGHT, CARET_UP, CARET_DOWN)
"""List of supported markers"""
_VERTEX_SHADER = """
@@ -640,7 +652,110 @@ class _Points2D(object):
return 0.0;
}
}
- """
+ """,
+ HEART: """
+ float alphaSymbol(vec2 coord, float size) {
+ coord = (coord - 0.5) * 2.;
+ coord *= 0.75;
+ coord.y += 0.25;
+ float a = atan(coord.x,-coord.y)/3.141593;
+ float r = length(coord);
+ float h = abs(a);
+ float d = (13.0*h - 22.0*h*h + 10.0*h*h*h)/(6.0-5.0*h);
+ float res = clamp(r-d, 0., 1.);
+ // antialiasing
+ res = smoothstep(0.1, 0.001, res);
+ return res;
+ }
+ """,
+ TICK_LEFT: """
+ float alphaSymbol(vec2 coord, float size) {
+ coord = size * (coord - 0.5);
+ float dy = abs(coord.y);
+ if (dy < 0.5 && coord.x < 0.5) {
+ return 1.0;
+ } else {
+ return 0.0;
+ }
+ }
+ """,
+ TICK_RIGHT: """
+ float alphaSymbol(vec2 coord, float size) {
+ coord = size * (coord - 0.5);
+ float dy = abs(coord.y);
+ if (dy < 0.5 && coord.x > -0.5) {
+ return 1.0;
+ } else {
+ return 0.0;
+ }
+ }
+ """,
+ TICK_UP: """
+ float alphaSymbol(vec2 coord, float size) {
+ coord = size * (coord - 0.5);
+ float dx = abs(coord.x);
+ if (dx < 0.5 && coord.y < 0.5) {
+ return 1.0;
+ } else {
+ return 0.0;
+ }
+ }
+ """,
+ TICK_DOWN: """
+ float alphaSymbol(vec2 coord, float size) {
+ coord = size * (coord - 0.5);
+ float dx = abs(coord.x);
+ if (dx < 0.5 && coord.y > -0.5) {
+ return 1.0;
+ } else {
+ return 0.0;
+ }
+ }
+ """,
+ CARET_LEFT: """
+ float alphaSymbol(vec2 coord, float size) {
+ coord = size * (coord - 0.5);
+ float d = abs(coord.x) - abs(coord.y);
+ if (d >= -0.1 && coord.x > 0.5) {
+ return smoothstep(-0.1, 0.1, d);
+ } else {
+ return 0.0;
+ }
+ }
+ """,
+ CARET_RIGHT: """
+ float alphaSymbol(vec2 coord, float size) {
+ coord = size * (coord - 0.5);
+ float d = abs(coord.x) - abs(coord.y);
+ if (d >= -0.1 && coord.x < 0.5) {
+ return smoothstep(-0.1, 0.1, d);
+ } else {
+ return 0.0;
+ }
+ }
+ """,
+ CARET_UP: """
+ float alphaSymbol(vec2 coord, float size) {
+ coord = size * (coord - 0.5);
+ float d = abs(coord.y) - abs(coord.x);
+ if (d >= -0.1 && coord.y > 0.5) {
+ return smoothstep(-0.1, 0.1, d);
+ } else {
+ return 0.0;
+ }
+ }
+ """,
+ CARET_DOWN: """
+ float alphaSymbol(vec2 coord, float size) {
+ coord = size * (coord - 0.5);
+ float d = abs(coord.y) - abs(coord.x);
+ if (d >= -0.1 && coord.y < 0.5) {
+ return smoothstep(-0.1, 0.1, d);
+ } else {
+ return 0.0;
+ }
+ }
+ """,
}
_FRAGMENT_SHADER_TEMPLATE = """
@@ -964,6 +1079,7 @@ class GLPlotCurve2D(object):
markerColor=(0., 0., 0., 1.),
markerSize=7,
fillColor=None,
+ baseline=None,
isYLog=False):
self.colorData = colorData
@@ -1003,11 +1119,28 @@ class GLPlotCurve2D(object):
self.offset = 0., 0.
self.xData = xData
self.yData = yData
-
if fillColor is not None:
+ def deduce_baseline(baseline):
+ if baseline is None:
+ _baseline = 0
+ else:
+ _baseline = baseline
+ if not isinstance(_baseline, numpy.ndarray):
+ _baseline = numpy.repeat(_baseline,
+ len(self.xData))
+ if isYLog is True:
+ with warnings.catch_warnings(): # Ignore NaN comparison warnings
+ warnings.simplefilter('ignore',
+ category=RuntimeWarning)
+ log_val = numpy.log10(_baseline)
+ _baseline = numpy.where(_baseline>0.0, log_val, -38)
+ return _baseline
+
+ _baseline = deduce_baseline(baseline)
+
# Use different baseline depending of Y log scale
self.fill = _Fill2D(self.xData, self.yData,
- baseline=-38 if isYLog else 0,
+ baseline=_baseline,
color=fillColor,
offset=self.offset)
else:
@@ -1129,7 +1262,7 @@ class GLPlotCurve2D(object):
and the segment [i-1, i] is not tested for picking.
:return: The indices of the picked data
- :rtype: list of int
+ :rtype: Union[List[int],None]
"""
if (self.marker is None and self.lineStyle is None) or \
self.xMin > xPickMax or xPickMin > self.xMax or \
@@ -1209,4 +1342,4 @@ class GLPlotCurve2D(object):
(self.yData >= yPickMin) &
(self.yData <= yPickMax))[0].tolist()
- return indices
+ return tuple(indices) if len(indices) > 0 else None
diff --git a/silx/gui/plot/backends/glutils/GLPlotImage.py b/silx/gui/plot/backends/glutils/GLPlotImage.py
index 6f3c487..5d79023 100644
--- a/silx/gui/plot/backends/glutils/GLPlotImage.py
+++ b/silx/gui/plot/backends/glutils/GLPlotImage.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2014-2018 European Synchrotron Radiation Facility
+# Copyright (c) 2014-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -56,7 +56,7 @@ class _GLPlotData2D(object):
sx, sy = self.scale
col = int((x - ox) / sx)
row = int((y - oy) / sy)
- return col, row
+ return ((row, col),)
else:
return None
diff --git a/silx/gui/plot/backends/glutils/GLPlotTriangles.py b/silx/gui/plot/backends/glutils/GLPlotTriangles.py
index c756749..7aeb5ab 100644
--- a/silx/gui/plot/backends/glutils/GLPlotTriangles.py
+++ b/silx/gui/plot/backends/glutils/GLPlotTriangles.py
@@ -114,11 +114,12 @@ class GLPlotTriangles(object):
:param float x: X coordinates in plot data frame
:param float y: Y coordinates in plot data frame
:return: List of picked data point indices
- :rtype: numpy.ndarray
+ :rtype: Union[List[int],None]
"""
if (x < self.xMin or x > self.xMax or
y < self.yMin or y > self.yMax):
- return ()
+ return None
+
xPts, yPts = self.__x_y_color[:2]
if self.__picking_triangles is None:
self.__picking_triangles = numpy.zeros(
@@ -135,9 +136,9 @@ class GLPlotTriangles(object):
# Sorted from furthest to closest point
dists = (xPts[indices] - x) ** 2 + (yPts[indices] - y) ** 2
- indices = indices[numpy.flip(numpy.argsort(dists))]
+ indices = indices[numpy.flip(numpy.argsort(dists), axis=0)]
- return tuple(indices)
+ return tuple(indices) if len(indices) > 0 else None
def discard(self):
"""Release resources on the GPU"""
diff --git a/silx/gui/plot/items/__init__.py b/silx/gui/plot/items/__init__.py
index f3a36db..7eff1d0 100644
--- a/silx/gui/plot/items/__init__.py
+++ b/silx/gui/plot/items/__init__.py
@@ -40,11 +40,11 @@ from .complex import ImageComplexData # noqa
from .curve import Curve, CurveStyle # noqa
from .histogram import Histogram # noqa
from .image import ImageBase, ImageData, ImageRgba, MaskImageData # noqa
-from .shape import Shape # noqa
+from .shape import Shape, BoundingRect # noqa
from .scatter import Scatter # noqa
from .marker import MarkerBase, Marker, XMarker, YMarker # noqa
from .axis import Axis, XAxis, YAxis, YRightAxis
-DATA_ITEMS = ImageComplexData, Curve, Histogram, ImageBase, Scatter
+DATA_ITEMS = ImageComplexData, Curve, Histogram, ImageBase, Scatter, BoundingRect
"""Classes of items representing data and to consider to compute data bounds.
"""
diff --git a/silx/gui/plot/items/_pick.py b/silx/gui/plot/items/_pick.py
new file mode 100644
index 0000000..14078fd
--- /dev/null
+++ b/silx/gui/plot/items/_pick.py
@@ -0,0 +1,70 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2019 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""This module provides classes supporting item picking."""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "04/06/2019"
+
+import numpy
+
+
+class PickingResult(object):
+ """Class to access picking information in a :class:`PlotWidget`"""
+
+ def __init__(self, item, indices=None):
+ """Init
+
+ :param item: The picked item
+ :param numpy.ndarray indices: Array-like of indices of picked data.
+ Either 1D or 2D with dim0: data dimension and dim1: indices.
+ No copy is made.
+ """
+ self._item = item
+
+ if indices is None or len(indices) == 0:
+ self._indices = None
+ else:
+ self._indices = numpy.array(indices, copy=False, dtype=numpy.int)
+
+ def getItem(self):
+ """Returns the item this results corresponds to."""
+ return self._item
+
+ def getIndices(self, copy=True):
+ """Returns indices of picked data.
+
+ If data is 1D, it returns a numpy.ndarray, otherwise
+ it returns a tuple with as many numpy.ndarray as there are
+ dimensions in the data.
+
+ :param bool copy: True (default) to get a copy,
+ False to return internal arrays
+ :rtype: Union[None,numpy.ndarray,List[numpy.ndarray]]
+ """
+ if self._indices is None:
+ return None
+ indices = numpy.array(self._indices, copy=copy)
+ return indices if indices.ndim == 1 else tuple(indices)
diff --git a/silx/gui/plot/items/complex.py b/silx/gui/plot/items/complex.py
index 3869a05..988022a 100644
--- a/silx/gui/plot/items/complex.py
+++ b/silx/gui/plot/items/complex.py
@@ -113,6 +113,16 @@ class ImageComplexData(ImageBase, ColormapMixIn, ComplexMixIn):
colored phase + amplitude.
"""
+ _SUPPORTED_COMPLEX_MODES = (
+ ComplexMixIn.ComplexMode.ABSOLUTE,
+ ComplexMixIn.ComplexMode.PHASE,
+ ComplexMixIn.ComplexMode.REAL,
+ ComplexMixIn.ComplexMode.IMAGINARY,
+ ComplexMixIn.ComplexMode.AMPLITUDE_PHASE,
+ ComplexMixIn.ComplexMode.LOG10_AMPLITUDE_PHASE,
+ ComplexMixIn.ComplexMode.SQUARE_AMPLITUDE)
+ """Overrides supported ComplexMode"""
+
def __init__(self):
ImageBase.__init__(self)
ColormapMixIn.__init__(self)
@@ -161,12 +171,9 @@ class ImageComplexData(ImageBase, ColormapMixIn, ComplexMixIn):
return None # No data to display
return backend.addImage(data,
- legend=self.getLegend(),
origin=self.getOrigin(),
scale=self.getScale(),
z=self.getZValue(),
- selectable=self.isSelectable(),
- draggable=self.isDraggable(),
colormap=colormap,
alpha=self.getAlpha())
diff --git a/silx/gui/plot/items/core.py b/silx/gui/plot/items/core.py
index e7342b0..6d6575b 100644
--- a/silx/gui/plot/items/core.py
+++ b/silx/gui/plot/items/core.py
@@ -47,6 +47,7 @@ from ....utils.enum import Enum as _Enum
from ... import qt
from ... import colors
from ...colors import Colormap
+from ._pick import PickingResult
from silx import config
@@ -136,6 +137,12 @@ class ItemChangedType(enum.Enum):
COMPLEX_MODE = 'complexModeChanged'
"""Item's complex data visualization mode changed flag."""
+ NAME = 'nameChanged'
+ """Item's name changed flag."""
+
+ EDITABLE = 'editableChanged'
+ """Item's editable state changed flags."""
+
class Item(qt.QObject):
"""Description of an item of the plot"""
@@ -330,6 +337,26 @@ class Item(qt.QObject):
backend.remove(self._backendRenderer)
self._backendRenderer = None
+ def pick(self, x, y):
+ """Run picking test on this item
+
+ :param float x: The x pixel coord where to pick.
+ :param float y: The y pixel coord where to pick.
+ :return: None if not picked, else the picked position information
+ :rtype: Union[None,PickingResult]
+ """
+ if not self.isVisible() or self._backendRenderer is None:
+ return None
+ plot = self.getPlot()
+ if plot is None:
+ return None
+
+ indices = plot._backend.pickItem(x, y, self._backendRenderer)
+ if indices is None:
+ return None
+ else:
+ return PickingResult(self, indices if len(indices) != 0 else None)
+
# Mix-in classes ##############################################################
@@ -471,6 +498,17 @@ class SymbolMixIn(ItemMixInBase):
('x', 'Cross'),
('.', 'Point'),
(',', 'Pixel'),
+ ('|', 'Vertical line'),
+ ('_', 'Horizontal line'),
+ ('tickleft', 'Tick left'),
+ ('tickright', 'Tick right'),
+ ('tickup', 'Tick up'),
+ ('tickdown', 'Tick down'),
+ ('caretleft', 'Caret left'),
+ ('caretright', 'Caret right'),
+ ('caretup', 'Caret up'),
+ ('caretdown', 'Caret down'),
+ (u'\u2665', 'Heart'),
('', 'None')))
"""Dict of supported symbols"""
@@ -781,6 +819,7 @@ class ComplexMixIn(ItemMixInBase):
class ComplexMode(_Enum):
"""Identify available display mode for complex"""
+ NONE = 'none'
ABSOLUTE = 'amplitude'
PHASE = 'phase'
REAL = 'real'
@@ -884,8 +923,54 @@ class ScatterVisualizationMixIn(ItemMixInBase):
This is based on Delaunay triangulation
"""
+ REGULAR_GRID = 'regular_grid'
+ """Display scatter plot as an image.
+
+ It expects the points to be the intersection of a regular grid,
+ and the order of points following that of an image.
+ First line, then second one, and always in the same direction
+ (either all lines from left to right or all from right to left).
+ """
+
+ IRREGULAR_GRID = 'irregular_grid'
+ """Display scatter plot as contiguous quadrilaterals.
+
+ It expects the points to be the intersection of an irregular grid,
+ and the order of points following that of an image.
+ First line, then second one, and always in the same direction
+ (either all lines from left to right or all from right to left).
+ """
+
+ @enum.unique
+ class VisualizationParameter(_Enum):
+ """Different parameter names for scatter plot visualizations"""
+
+ GRID_MAJOR_ORDER = 'grid_major_order'
+ """The major order of points in the regular grid.
+
+ Either 'row' (row-major, fast X) or 'column' (column-major, fast Y).
+ """
+
+ GRID_BOUNDS = 'grid_bounds'
+ """The expected range in data coordinates of the regular grid.
+
+ A 2-tuple of 2-tuple: (begin (x, y), end (x, y)).
+ This provides the data coordinates of the first point and the expected
+ last on.
+ As for `GRID_SHAPE`, this can be wider than the current data.
+ """
+
+ GRID_SHAPE = 'grid_shape'
+ """The expected size of the regular grid (height, width).
+
+ The given shape can be wider than the number of points,
+ in which case the grid is not fully filled.
+ """
+
def __init__(self):
self.__visualization = self.Visualization.POINTS
+ self.__parameters = dict( # Init parameters to None
+ (parameter, None) for parameter in self.VisualizationParameter)
@classmethod
def supportedVisualizations(cls):
@@ -929,6 +1014,54 @@ class ScatterVisualizationMixIn(ItemMixInBase):
"""
return self.__visualization
+ def setVisualizationParameter(self, parameter, value=None):
+ """Set the given visualization parameter.
+
+ :param Union[str,VisualizationParameter] parameter:
+ The name of the parameter to set
+ :param value: The value to use for this parameter
+ Set to None to automatically set the parameter
+ :raises ValueError: If parameter is not supported
+ :return: True if parameter was set, False if is was already set
+ :rtype: bool
+ """
+ parameter = self.VisualizationParameter.from_value(parameter)
+
+ if self.__parameters[parameter] != value:
+ self.__parameters[parameter] = value
+ self._updated(ItemChangedType.VISUALIZATION_MODE)
+ return True
+ return False
+
+ def getVisualizationParameter(self, parameter):
+ """Returns the value of the given visualization parameter.
+
+ This method returns the parameter as set by
+ :meth:`setVisualizationParameter`.
+
+ :param parameter: The name of the parameter to retrieve
+ :returns: The value previously set or None if automatically set
+ :raises ValueError: If parameter is not supported
+ """
+ if parameter not in self.VisualizationParameter:
+ raise ValueError("parameter not supported: %s", parameter)
+
+ return self.__parameters[parameter]
+
+ def getCurrentVisualizationParameter(self, parameter):
+ """Returns the current value of the given visualization parameter.
+
+ If the parameter was set by :meth:`setVisualizationParameter` to
+ a value that is not None, this value is returned;
+ else the current value that is automatically computed is returned.
+
+ :param parameter: The name of the parameter to retrieve
+ :returns: The current value (either set or automatically computed)
+ :raises ValueError: If parameter is not supported
+ """
+ # Override in subclass to provide automatically computed parameters
+ return self.getVisualizationParameter(parameter)
+
class PointsBase(Item, SymbolMixIn, AlphaMixIn):
"""Base class for :class:`Curve` and :class:`Scatter`"""
@@ -1224,3 +1357,32 @@ class PointsBase(Item, SymbolMixIn, AlphaMixIn):
if plot is not None:
plot._invalidateDataRange()
self._updated(ItemChangedType.DATA)
+
+
+class BaselineMixIn(object):
+ """Base class for Baseline mix-in"""
+ def __init__(self, baseline=None):
+ self._baseline = baseline
+
+ def _setBaseline(self, baseline):
+ """
+ Set baseline value
+
+ :param baseline: baseline value(s)
+ :type: Union[None,float,numpy.ndarray]
+ """
+ if (isinstance(baseline, abc.Iterable)):
+ baseline = numpy.array(baseline)
+ self._baseline = baseline
+
+ def getBaseline(self, copy=True):
+ """
+
+ :param bool copy:
+ :return: histogram baseline
+ :rtype: Union[None,float,numpy.ndarray]
+ """
+ if isinstance(self._baseline, numpy.ndarray):
+ return numpy.array(self._baseline, copy=True)
+ else:
+ return self._baseline
diff --git a/silx/gui/plot/items/curve.py b/silx/gui/plot/items/curve.py
index 439af33..5853ef5 100644
--- a/silx/gui/plot/items/curve.py
+++ b/silx/gui/plot/items/curve.py
@@ -38,7 +38,8 @@ import six
from ....utils.deprecation import deprecated
from ... import colors
from .core import (PointsBase, LabelsMixIn, ColorMixIn, YAxisMixIn,
- FillMixIn, LineMixIn, SymbolMixIn, ItemChangedType)
+ FillMixIn, LineMixIn, SymbolMixIn, ItemChangedType,
+ BaselineMixIn)
_logger = logging.getLogger(__name__)
@@ -151,7 +152,8 @@ class CurveStyle(object):
return False
-class Curve(PointsBase, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn, LineMixIn):
+class Curve(PointsBase, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn,
+ LineMixIn, BaselineMixIn):
"""Description of a curve"""
_DEFAULT_Z_LAYER = 1
@@ -169,6 +171,8 @@ class Curve(PointsBase, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn, LineMixI
_DEFAULT_HIGHLIGHT_STYLE = CurveStyle(color='black')
"""Default highlight style of the item"""
+ _DEFAULT_BASELINE = None
+
def __init__(self):
PointsBase.__init__(self)
ColorMixIn.__init__(self)
@@ -176,9 +180,11 @@ class Curve(PointsBase, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn, LineMixI
FillMixIn.__init__(self)
LabelsMixIn.__init__(self)
LineMixIn.__init__(self)
+ BaselineMixIn.__init__(self)
self._highlightStyle = self._DEFAULT_HIGHLIGHT_STYLE
self._highlighted = False
+ self._setBaseline(Curve._DEFAULT_BASELINE)
self.sigItemChanged.connect(self.__itemChanged)
@@ -200,7 +206,7 @@ class Curve(PointsBase, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn, LineMixI
style = self.getCurrentStyle()
- return backend.addCurve(xFiltered, yFiltered, self.getLegend(),
+ return backend.addCurve(xFiltered, yFiltered,
color=style.getColor(),
symbol=style.getSymbol(),
linestyle=style.getLineStyle(),
@@ -209,10 +215,10 @@ class Curve(PointsBase, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn, LineMixI
xerror=xerror,
yerror=yerror,
z=self.getZValue(),
- selectable=self.isSelectable(),
fill=self.isFill(),
alpha=self.getAlpha(),
- symbolsize=style.getSymbolSize())
+ symbolsize=style.getSymbolSize(),
+ baseline=self.getBaseline(copy=False))
def __getitem__(self, item):
"""Compatibility with PyMca and silx <= 0.4.0"""
@@ -241,7 +247,7 @@ class Curve(PointsBase, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn, LineMixI
'yerror': self.getYErrorData(copy=False),
'z': self.getZValue(),
'selectable': self.isSelectable(),
- 'fill': self.isFill()
+ 'fill': self.isFill(),
}
return params
else:
@@ -361,3 +367,25 @@ class Curve(PointsBase, ColorMixIn, YAxisMixIn, FillMixIn, LabelsMixIn, LineMixI
:rtype: 4-tuple of float in [0, 1]
"""
return self.getCurrentStyle().getColor()
+
+ def setData(self, x, y, xerror=None, yerror=None, baseline=None, copy=True):
+ """Set the data of the curve.
+
+ :param numpy.ndarray x: The data corresponding to the x coordinates.
+ :param numpy.ndarray y: The data corresponding to the y coordinates.
+ :param xerror: Values with the uncertainties on the x values
+ :type xerror: A float, or a numpy.ndarray of float32.
+ If it is an array, it can either be a 1D array of
+ same length as the data or a 2D array with 2 rows
+ of same length as the data: row 0 for positive errors,
+ row 1 for negative errors.
+ :param yerror: Values with the uncertainties on the y values.
+ :type yerror: A float, or a numpy.ndarray of float32. See xerror.
+ :param baseline: curve baseline
+ :type baseline: Union[None,float,numpy.ndarray]
+ :param bool copy: True make a copy of the data (default),
+ False to use provided arrays.
+ """
+ PointsBase.setData(self, x=x, y=y, xerror=xerror, yerror=yerror,
+ copy=copy)
+ self._setBaseline(baseline=baseline)
diff --git a/silx/gui/plot/items/histogram.py b/silx/gui/plot/items/histogram.py
index a1d6586..993c0f0 100644
--- a/silx/gui/plot/items/histogram.py
+++ b/silx/gui/plot/items/histogram.py
@@ -8,7 +8,7 @@
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
+# furnished to do so, subject to the following conditions::t
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
@@ -32,8 +32,13 @@ __date__ = "28/08/2018"
import logging
import numpy
+from collections import OrderedDict, namedtuple
+try:
+ from collections import abc
+except ImportError: # Python2 support
+ import collections as abc
-from .core import (Item, AlphaMixIn, ColorMixIn, FillMixIn,
+from .core import (Item, AlphaMixIn, BaselineMixIn, ColorMixIn, FillMixIn,
LineMixIn, YAxisMixIn, ItemChangedType)
_logger = logging.getLogger(__name__)
@@ -96,7 +101,7 @@ def _getHistogramCurve(histogram, edges):
# TODO: Yerror, test log scale
class Histogram(Item, AlphaMixIn, ColorMixIn, FillMixIn,
- LineMixIn, YAxisMixIn):
+ LineMixIn, YAxisMixIn, BaselineMixIn):
"""Description of an histogram"""
_DEFAULT_Z_LAYER = 1
@@ -111,9 +116,12 @@ class Histogram(Item, AlphaMixIn, ColorMixIn, FillMixIn,
_DEFAULT_LINESTYLE = '-'
"""Default line style of the histogram"""
+ _DEFAULT_BASELINE = None
+
def __init__(self):
Item.__init__(self)
AlphaMixIn.__init__(self)
+ BaselineMixIn.__init__(self)
ColorMixIn.__init__(self)
FillMixIn.__init__(self)
LineMixIn.__init__(self)
@@ -121,10 +129,11 @@ class Histogram(Item, AlphaMixIn, ColorMixIn, FillMixIn,
self._histogram = ()
self._edges = ()
+ self._setBaseline(Histogram._DEFAULT_BASELINE)
def _addBackendRenderer(self, backend):
"""Update backend renderer"""
- values, edges = self.getData(copy=False)
+ values, edges, baseline = self.getData(copy=False)
if values.size == 0:
return None # No data to display, do not add renderer
@@ -153,7 +162,7 @@ class Histogram(Item, AlphaMixIn, ColorMixIn, FillMixIn,
x[clipped] = numpy.nan
y[clipped] = numpy.nan
- return backend.addCurve(x, y, self.getLegend(),
+ return backend.addCurve(x, y,
color=self.getColor(),
symbol='',
linestyle=self.getLineStyle(),
@@ -162,13 +171,13 @@ class Histogram(Item, AlphaMixIn, ColorMixIn, FillMixIn,
xerror=None,
yerror=None,
z=self.getZValue(),
- selectable=self.isSelectable(),
fill=self.isFill(),
alpha=self.getAlpha(),
+ baseline=baseline,
symbolsize=1)
def _getBounds(self):
- values, edges = self.getData(copy=False)
+ values, edges, baseline = self.getData(copy=False)
plot = self.getPlot()
if plot is not None:
@@ -243,16 +252,19 @@ class Histogram(Item, AlphaMixIn, ColorMixIn, FillMixIn,
return numpy.array(self._edges, copy=copy)
def getData(self, copy=True):
- """Return the histogram values and the bin edges
+ """Return the histogram values, bin edges and baseline
:param copy: True (Default) to get a copy,
False to use internal representation (do not modify!)
:returns: (N histogram value, N+1 bin edges)
:rtype: 2-tuple of numpy.nadarray
"""
- return self.getValueData(copy), self.getBinEdgesData(copy)
+ return (self.getValueData(copy),
+ self.getBinEdgesData(copy),
+ self.getBaseline(copy))
- def setData(self, histogram, edges, align='center', copy=True):
+ def setData(self, histogram, edges, align='center', baseline=None,
+ copy=True):
"""Set the histogram values and bin edges.
:param numpy.ndarray histogram: The values of the histogram.
@@ -264,6 +276,8 @@ class Histogram(Item, AlphaMixIn, ColorMixIn, FillMixIn,
In case histogram values and edges have the same length N,
the N+1 bin edges are computed according to the alignment in:
'center' (default), 'left', 'right'.
+ :param baseline: histogram baseline
+ :type baseline: Union[None,float,numpy.ndarray]
:param bool copy: True make a copy of the data (default),
False to use provided arrays.
"""
@@ -285,10 +299,18 @@ class Histogram(Item, AlphaMixIn, ColorMixIn, FillMixIn,
# Check that bin edges are monotonic
edgesDiff = numpy.diff(edges)
assert numpy.all(edgesDiff >= 0) or numpy.all(edgesDiff <= 0)
-
+ # manage baseline
+ if (isinstance(baseline, abc.Iterable)):
+ baseline = numpy.array(baseline)
+ if baseline.size == histogram.size:
+ new_baseline = numpy.empty(baseline.shape[0] * 2)
+ for i_value, value in enumerate(baseline):
+ new_baseline[i_value*2:i_value*2+2] = value
+ baseline = new_baseline
self._histogram = histogram
self._edges = edges
self._alignement = align
+ self._setBaseline(baseline)
if self.isVisible():
plot = self.getPlot()
diff --git a/silx/gui/plot/items/image.py b/silx/gui/plot/items/image.py
index d74f4d3..44cb70f 100644
--- a/silx/gui/plot/items/image.py
+++ b/silx/gui/plot/items/image.py
@@ -42,6 +42,7 @@ import numpy
from ....utils.proxy import docstring
from .core import (Item, LabelsMixIn, DraggableMixIn, ColormapMixIn,
AlphaMixIn, ItemChangedType)
+from ._pick import PickingResult
_logger = logging.getLogger(__name__)
@@ -142,6 +143,25 @@ class ImageBase(Item, LabelsMixIn, DraggableMixIn, AlphaMixIn):
plot._invalidateDataRange()
super(ImageBase, self).setVisible(visible)
+ @docstring(Item)
+ def pick(self, x, y):
+ if super(ImageBase, self).pick(x, y) is not None:
+ plot = self.getPlot()
+ if plot is None:
+ return None
+
+ dataPos = plot.pixelToData(x, y)
+ if dataPos is None:
+ return None
+
+ origin = self.getOrigin()
+ scale = self.getScale()
+ column = int((dataPos[0] - origin[0]) / float(scale[0]))
+ row = int((dataPos[1] - origin[1]) / float(scale[1]))
+ return PickingResult(self, ([row], [column]))
+
+ return None
+
def _isPlotLinear(self, plot):
"""Return True if plot only uses linear scale for both of x and y
axes."""
@@ -282,12 +302,9 @@ class ImageData(ImageBase, ColormapMixIn):
return None # No data to display
return backend.addImage(dataToUse,
- legend=self.getLegend(),
origin=self.getOrigin(),
scale=self.getScale(),
z=self.getZValue(),
- selectable=self.isSelectable(),
- draggable=self.isDraggable(),
colormap=self.getColormap(),
alpha=self.getAlpha())
@@ -415,12 +432,9 @@ class ImageRgba(ImageBase):
return None # No data to display
return backend.addImage(data,
- legend=self.getLegend(),
origin=self.getOrigin(),
scale=self.getScale(),
z=self.getZValue(),
- selectable=self.isSelectable(),
- draggable=self.isDraggable(),
colormap=None,
alpha=self.getAlpha())
diff --git a/silx/gui/plot/items/marker.py b/silx/gui/plot/items/marker.py
index 80ca0b6..f5a1689 100644..100755
--- a/silx/gui/plot/items/marker.py
+++ b/silx/gui/plot/items/marker.py
@@ -34,13 +34,13 @@ import logging
from ....utils.proxy import docstring
from .core import (Item, DraggableMixIn, ColorMixIn, LineMixIn, SymbolMixIn,
- ItemChangedType)
+ ItemChangedType, YAxisMixIn)
_logger = logging.getLogger(__name__)
-class MarkerBase(Item, DraggableMixIn, ColorMixIn):
+class MarkerBase(Item, DraggableMixIn, ColorMixIn, YAxisMixIn):
"""Base class for markers"""
_DEFAULT_COLOR = (0., 0., 0., 1.)
@@ -50,6 +50,7 @@ class MarkerBase(Item, DraggableMixIn, ColorMixIn):
Item.__init__(self)
DraggableMixIn.__init__(self)
ColorMixIn.__init__(self)
+ YAxisMixIn.__init__(self)
self._text = ''
self._x = None
@@ -62,15 +63,13 @@ class MarkerBase(Item, DraggableMixIn, ColorMixIn):
return backend.addMarker(
x=self.getXPosition(),
y=self.getYPosition(),
- legend=self.getLegend(),
text=self.getText(),
color=self.getColor(),
- selectable=self.isSelectable(),
- draggable=self.isDraggable(),
symbol=symbol,
linestyle=linestyle,
linewidth=linewidth,
- constraint=self.getConstraint())
+ constraint=self.getConstraint(),
+ yaxis=self.getYAxis())
def _addBackendRenderer(self, backend):
"""Update backend renderer"""
@@ -81,13 +80,11 @@ class MarkerBase(Item, DraggableMixIn, ColorMixIn):
self.setPosition(to[0], to[1])
def isOverlay(self):
- """Return true if marker is drawn as an overlay.
-
- A marker is an overlay if it is draggable.
+ """Returns True: A marker is always rendered as an overlay.
:rtype: bool
"""
- return self.isDraggable()
+ return True
def getText(self):
"""Returns marker text.
diff --git a/silx/gui/plot/items/roi.py b/silx/gui/plot/items/roi.py
index 65831be..dcad943 100644
--- a/silx/gui/plot/items/roi.py
+++ b/silx/gui/plot/items/roi.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2018 European Synchrotron Radiation Facility
+# Copyright (c) 2018-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -40,12 +40,51 @@ from ....utils.weakref import WeakList
from ... import qt
from .. import items
from ...colors import rgba
+import silx.utils.deprecation
+from silx.utils.proxy import docstring
logger = logging.getLogger(__name__)
-class RegionOfInterest(qt.QObject):
+class _RegionOfInterestBase(qt.QObject):
+ """Base class of 1D and 2D region of interest
+
+ :param QObject parent: See QObject
+ :param str name: The name of the ROI
+ """
+
+ sigItemChanged = qt.Signal(object)
+ """Signal emitted when item has changed.
+
+ It provides a flag describing which property of the item has changed.
+ See :class:`ItemChangedType` for flags description.
+ """
+
+ def __init__(self, parent=None, name=''):
+ qt.QObject.__init__(self)
+ self.__name = str(name)
+
+ def getName(self):
+ """Returns the name of the ROI
+
+ :return: name of the region of interest
+ :rtype: str
+ """
+ return self.__name
+
+ def setName(self, name):
+ """Set the name of the ROI
+
+ :param str name: name of the region of interest
+ """
+ name = str(name)
+ if self.__name != name:
+ self.__name = name
+ self.sigItemChanged.emit(items.ItemChangedType.NAME)
+
+
+class RegionOfInterest(_RegionOfInterestBase):
"""Object describing a region of interest in a plot.
:param QObject parent:
@@ -55,7 +94,7 @@ class RegionOfInterest(qt.QObject):
_kind = None
"""Label for this kind of ROI.
- Should be setted by inherited classes to custom the ROI manager widget.
+ Should be set by inherited classes to custom the ROI manager widget.
"""
sigRegionChanged = qt.Signal()
@@ -65,15 +104,20 @@ class RegionOfInterest(qt.QObject):
# Avoid circular dependancy
from ..tools import roi as roi_tools
assert parent is None or isinstance(parent, roi_tools.RegionOfInterestManager)
- qt.QObject.__init__(self, parent)
+ _RegionOfInterestBase.__init__(self, parent, '')
self._color = rgba('red')
self._items = WeakList()
self._editAnchors = WeakList()
self._points = None
- self._label = ''
self._labelItem = None
self._editable = False
self._visible = True
+ self.sigItemChanged.connect(self.__itemChanged)
+
+ def __itemChanged(self, event):
+ """Handle name change"""
+ if event == items.ItemChangedType.NAME:
+ self._updateLabelItem(self.getName())
def __del__(self):
# Clean-up plot items
@@ -140,22 +184,27 @@ class RegionOfInterest(qt.QObject):
if isinstance(item, items.ColorMixIn):
item.setColor(rgbaColor)
+ self.sigItemChanged.emit(items.ItemChangedType.COLOR)
+
+ @silx.utils.deprecation.deprecated(reason='API modification',
+ replacement='getName()',
+ since_version=0.12)
def getLabel(self):
"""Returns the label displayed for this ROI.
:rtype: str
"""
- return self._label
+ return self.getName()
+ @silx.utils.deprecation.deprecated(reason='API modification',
+ replacement='setName(name)',
+ since_version=0.12)
def setLabel(self, label):
"""Set the label displayed with this ROI.
:param str label: The text label to display
"""
- label = str(label)
- if label != self._label:
- self._label = label
- self._updateLabelItem(label)
+ self.setName(name=label)
def isEditable(self):
"""Returns whether the ROI is editable by the user or not.
@@ -176,6 +225,7 @@ class RegionOfInterest(qt.QObject):
# Recreate plot items
# This can be avoided once marker.setDraggable is public
self._createPlotItems()
+ self.sigItemChanged.emit(items.ItemChangedType.EDITABLE)
def isVisible(self):
"""Returns whether the ROI is visible in the plot.
@@ -197,13 +247,13 @@ class RegionOfInterest(qt.QObject):
hide it.
"""
visible = bool(visible)
- if self._visible == visible:
- return
- self._visible = visible
- if self._labelItem is not None:
- self._labelItem.setVisible(visible)
- for item in self._items + self._editAnchors:
- item.setVisible(visible)
+ if self._visible != visible:
+ self._visible = visible
+ if self._labelItem is not None:
+ self._labelItem.setVisible(visible)
+ for item in self._items + self._editAnchors:
+ item.setVisible(visible)
+ self.sigItemChanged.emit(items.ItemChangedType.VISIBLE)
def _getControlPoints(self):
"""Returns the current ROI control points.
@@ -371,7 +421,7 @@ class RegionOfInterest(qt.QObject):
markerPos = self._getLabelPosition()
marker = items.Marker()
marker.setPosition(*markerPos)
- marker.setText(self.getLabel())
+ marker.setText(self.getName())
marker.setColor(rgba(self.getColor()))
marker.setSymbol('')
marker._setDraggable(False)
@@ -465,6 +515,12 @@ class PointROI(RegionOfInterest, items.SymbolMixIn):
_plotShape = "point"
"""Plot shape which is used for the first interaction"""
+ _DEFAULT_SYMBOL = '+'
+ """Default symbol of the PointROI
+
+ It overwrite the `SymbolMixIn` class attribte.
+ """
+
def __init__(self, parent=None):
items.SymbolMixIn.__init__(self)
RegionOfInterest.__init__(self, parent=parent)
@@ -488,31 +544,31 @@ class PointROI(RegionOfInterest, items.SymbolMixIn):
return None
def _updateLabelItem(self, label):
- if self.isEditable():
- item = self._editAnchors[0]
- else:
+ self._items[0].setText(label)
+
+ def _updateShape(self):
+ if len(self._items) > 0:
+ controlPoints = self._getControlPoints()
item = self._items[0]
- item.setText(label)
+ item.setPosition(*controlPoints[0])
+
+ def __positionChanged(self, event):
+ """Handle position changed events of the marker"""
+ if event is items.ItemChangedType.POSITION:
+ marker = self.sender()
+ if isinstance(marker, items.Marker):
+ self.setPosition(marker.getPosition())
def _createShapeItems(self, points):
- if self.isEditable():
- return []
marker = items.Marker()
marker.setPosition(points[0][0], points[0][1])
- marker.setText(self.getLabel())
- marker.setColor(rgba(self.getColor()))
+ marker.setText(self.getName())
marker.setSymbol(self.getSymbol())
marker.setSymbolSize(self.getSymbolSize())
- marker._setDraggable(False)
- return [marker]
-
- def _createAnchorItems(self, points):
- marker = items.Marker()
- marker.setPosition(points[0][0], points[0][1])
- marker.setText(self.getLabel())
+ marker.setColor(rgba(self.getColor()))
marker._setDraggable(self.isEditable())
- marker.setSymbol(self.getSymbol())
- marker.setSymbolSize(self.getSymbolSize())
+ if self.isEditable():
+ marker.sigItemChanged.connect(self.__positionChanged)
return [marker]
def __str__(self):
@@ -672,38 +728,31 @@ class HorizontalLineROI(RegionOfInterest, items.LineMixIn):
return None
def _updateLabelItem(self, label):
- if self.isEditable():
- item = self._editAnchors[0]
- else:
- item = self._items[0]
- item.setText(label)
+ self._items[0].setText(label)
def _updateShape(self):
- if not self.isEditable():
- if len(self._items) > 0:
- controlPoints = self._getControlPoints()
- item = self._items[0]
- item.setPosition(*controlPoints[0])
+ if len(self._items) > 0:
+ controlPoints = self._getControlPoints()
+ item = self._items[0]
+ item.setPosition(*controlPoints[0])
+
+ def __positionChanged(self, event):
+ """Handle position changed events of the marker"""
+ if event is items.ItemChangedType.POSITION:
+ marker = self.sender()
+ if isinstance(marker, items.YMarker):
+ self.setPosition(marker.getYPosition())
def _createShapeItems(self, points):
- if self.isEditable():
- return []
marker = items.YMarker()
marker.setPosition(points[0][0], points[0][1])
- marker.setText(self.getLabel())
+ marker.setText(self.getName())
marker.setColor(rgba(self.getColor()))
- marker._setDraggable(False)
marker.setLineWidth(self.getLineWidth())
marker.setLineStyle(self.getLineStyle())
- return [marker]
-
- def _createAnchorItems(self, points):
- marker = items.YMarker()
- marker.setPosition(points[0][0], points[0][1])
- marker.setText(self.getLabel())
marker._setDraggable(self.isEditable())
- marker.setLineWidth(self.getLineWidth())
- marker.setLineStyle(self.getLineStyle())
+ if self.isEditable():
+ marker.sigItemChanged.connect(self.__positionChanged)
return [marker]
def __str__(self):
@@ -749,38 +798,31 @@ class VerticalLineROI(RegionOfInterest, items.LineMixIn):
return None
def _updateLabelItem(self, label):
- if self.isEditable():
- item = self._editAnchors[0]
- else:
- item = self._items[0]
- item.setText(label)
+ self._items[0].setText(label)
def _updateShape(self):
- if not self.isEditable():
- if len(self._items) > 0:
- controlPoints = self._getControlPoints()
- item = self._items[0]
- item.setPosition(*controlPoints[0])
+ if len(self._items) > 0:
+ controlPoints = self._getControlPoints()
+ item = self._items[0]
+ item.setPosition(*controlPoints[0])
+
+ def __positionChanged(self, event):
+ """Handle position changed events of the marker"""
+ if event is items.ItemChangedType.POSITION:
+ marker = self.sender()
+ if isinstance(marker, items.XMarker):
+ self.setPosition(marker.getXPosition())
def _createShapeItems(self, points):
- if self.isEditable():
- return []
marker = items.XMarker()
marker.setPosition(points[0][0], points[0][1])
- marker.setText(self.getLabel())
+ marker.setText(self.getName())
marker.setColor(rgba(self.getColor()))
- marker._setDraggable(False)
marker.setLineWidth(self.getLineWidth())
marker.setLineStyle(self.getLineStyle())
- return [marker]
-
- def _createAnchorItems(self, points):
- marker = items.XMarker()
- marker.setPosition(points[0][0], points[0][1])
- marker.setText(self.getLabel())
marker._setDraggable(self.isEditable())
- marker.setLineWidth(self.getLineWidth())
- marker.setLineStyle(self.getLineStyle())
+ if self.isEditable():
+ marker.sigItemChanged.connect(self.__positionChanged)
return [marker]
def __str__(self):
diff --git a/silx/gui/plot/items/scatter.py b/silx/gui/plot/items/scatter.py
index b2f087b..50cc694 100644
--- a/silx/gui/plot/items/scatter.py
+++ b/silx/gui/plot/items/scatter.py
@@ -25,11 +25,15 @@
"""This module provides the :class:`Scatter` item of the :class:`Plot`.
"""
+from __future__ import division
+
+
__authors__ = ["T. Vincent", "P. Knobel"]
__license__ = "MIT"
__date__ = "29/03/2017"
+from collections import namedtuple
import logging
import threading
import numpy
@@ -37,10 +41,13 @@ import numpy
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor, CancelledError
+from ....utils.proxy import docstring
+from ....math.combo import min_max
from ....utils.weakref import WeakList
from .._utils.delaunay import delaunay
from .core import PointsBase, ColormapMixIn, ScatterVisualizationMixIn
from .axis import Axis
+from ._pick import PickingResult
_logger = logging.getLogger(__name__)
@@ -79,6 +86,184 @@ class _GreedyThreadPoolExecutor(ThreadPoolExecutor):
return future
+# Functions to guess grid shape from coordinates
+
+def _get_z_line_length(array):
+ """Return length of line if array is a Z-like 2D regular grid.
+
+ :param numpy.ndarray array: The 1D array of coordinates to check
+ :return: 0 if no line length could be found,
+ else the number of element per line.
+ :rtype: int
+ """
+ sign = numpy.sign(numpy.diff(array))
+ if len(sign) == 0 or sign[0] == 0: # We don't handle that
+ return 0
+ # Check this way to account for 0 sign (i.e., diff == 0)
+ beginnings = numpy.where(sign == - sign[0])[0] + 1
+ if len(beginnings) == 0:
+ return 0
+ length = beginnings[0]
+ if numpy.all(numpy.equal(numpy.diff(beginnings), length)):
+ return length
+ return 0
+
+
+def _guess_z_grid_shape(x, y):
+ """Guess the shape of a grid from (x, y) coordinates.
+
+ The grid might contain more elements than x and y,
+ as the last line might be partly filled.
+
+ :param numpy.ndarray x:
+ :paran numpy.ndarray y:
+ :returns: (order, (height, width)) of the regular grid,
+ or None if could not guess one.
+ 'order' is 'row' if X (i.e., column) is the fast dimension, else 'column'.
+ :rtype: Union[List(str,int),None]
+ """
+ width = _get_z_line_length(x)
+ if width != 0:
+ return 'row', (int(numpy.ceil(len(x) / width)), width)
+ else:
+ height = _get_z_line_length(y)
+ if height != 0:
+ return 'column', (height, int(numpy.ceil(len(y) / height)))
+ return None
+
+
+def is_monotonic(array):
+ """Returns whether array is monotonic (increasing or decreasing).
+
+ :param numpy.ndarray array: 1D array-like container.
+ :returns: 1 if array is monotonically increasing,
+ -1 if array is monotonically decreasing,
+ 0 if array is not monotonic
+ :rtype: int
+ """
+ diff = numpy.diff(numpy.ravel(array))
+ if numpy.all(diff >= 0):
+ return 1
+ elif numpy.all(diff <= 0):
+ return -1
+ else:
+ return 0
+
+
+def _guess_grid(x, y):
+ """Guess a regular grid from the points.
+
+ Result convention is (x, y)
+
+ :param numpy.ndarray x: X coordinates of the points
+ :param numpy.ndarray y: Y coordinates of the points
+ :returns: (order, (height, width)
+ order is 'row' or 'column'
+ :rtype: Union[List[str,List[int]],None]
+ """
+ x, y = numpy.ravel(x), numpy.ravel(y)
+
+ guess = _guess_z_grid_shape(x, y)
+ if guess is not None:
+ return guess
+
+ else:
+ # Cannot guess a regular grid
+ # Let's assume it's a single line
+ order = 'row' # or 'column' doesn't matter for a single line
+ y_monotonic = is_monotonic(y)
+ if is_monotonic(x) or y_monotonic: # we can guess a line
+ x_min, x_max = min_max(x)
+ y_min, y_max = min_max(y)
+
+ if not y_monotonic or x_max - x_min >= y_max - y_min:
+ # x only is monotonic or both are and X varies more
+ # line along X
+ shape = 1, len(x)
+ else:
+ # y only is monotonic or both are and Y varies more
+ # line along Y
+ shape = len(y), 1
+
+ else: # Cannot guess a line from the points
+ return None
+
+ return order, shape
+
+
+def _quadrilateral_grid_coords(points):
+ """Compute an irregular grid of quadrilaterals from a set of points
+
+ The input points are expected to lie on a grid.
+
+ :param numpy.ndarray points:
+ 3D data set of 2D input coordinates (height, width, 2)
+ height and width must be at least 2.
+ :return: 3D dataset of 2D coordinates of the grid (height+1, width+1, 2)
+ """
+ assert points.ndim == 3
+ assert points.shape[0] >= 2
+ assert points.shape[1] >= 2
+ assert points.shape[2] == 2
+
+ dim0, dim1 = points.shape[:2]
+ grid_points = numpy.zeros((dim0 + 1, dim1 + 1, 2), dtype=numpy.float64)
+
+ # Compute inner points as mean of 4 neighbours
+ neighbour_view = numpy.lib.stride_tricks.as_strided(
+ points,
+ shape=(dim0 - 1, dim1 - 1, 2, 2, points.shape[2]),
+ strides=points.strides[:2] + points.strides[:2] + points.strides[-1:], writeable=False)
+ inner_points = numpy.mean(neighbour_view, axis=(2, 3))
+ grid_points[1:-1, 1:-1] = inner_points
+
+ # Compute 'vertical' sides
+ # Alternative: grid_points[1:-1, [0, -1]] = points[:-1, [0, -1]] + points[1:, [0, -1]] - inner_points[:, [0, -1]]
+ grid_points[1:-1, [0, -1], 0] = points[:-1, [0, -1], 0] + points[1:, [0, -1], 0] - inner_points[:, [0, -1], 0]
+ grid_points[1:-1, [0, -1], 1] = inner_points[:, [0, -1], 1]
+
+ # Compute 'horizontal' sides
+ grid_points[[0, -1], 1:-1, 0] = inner_points[[0, -1], :, 0]
+ grid_points[[0, -1], 1:-1, 1] = points[[0, -1], :-1, 1] + points[[0, -1], 1:, 1] - inner_points[[0, -1], :, 1]
+
+ # Compute corners
+ d0, d1 = [0, 0, -1, -1], [0, -1, -1, 0]
+ grid_points[d0, d1] = 2 * points[d0, d1] - inner_points[d0, d1]
+ return grid_points
+
+
+def _quadrilateral_grid_as_triangles(points):
+ """Returns the points and indices to make a grid of quadirlaterals
+
+ :param numpy.ndarray points:
+ 3D array of points (height, width, 2)
+ :return: triangle corners (4 * N, 2), triangle indices (2 * N, 3)
+ With N = height * width, the number of input points
+ """
+ nbpoints = numpy.prod(points.shape[:2])
+
+ grid = _quadrilateral_grid_coords(points)
+ coords = numpy.empty((4 * nbpoints, 2), dtype=grid.dtype)
+ coords[::4] = grid[:-1, :-1].reshape(-1, 2)
+ coords[1::4] = grid[1:, :-1].reshape(-1, 2)
+ coords[2::4] = grid[:-1, 1:].reshape(-1, 2)
+ coords[3::4] = grid[1:, 1:].reshape(-1, 2)
+
+ indices = numpy.empty((2 * nbpoints, 3), dtype=numpy.uint32)
+ indices[::2, 0] = numpy.arange(0, 4 * nbpoints, 4)
+ indices[::2, 1] = numpy.arange(1, 4 * nbpoints, 4)
+ indices[::2, 2] = numpy.arange(2, 4 * nbpoints, 4)
+ indices[1::2, 0] = indices[::2, 1]
+ indices[1::2, 1] = indices[::2, 2]
+ indices[1::2, 2] = numpy.arange(3, 4 * nbpoints, 4)
+
+ return coords, indices
+
+
+_RegularGridInfo = namedtuple(
+ '_RegularGridInfo', ['bounds', 'origin', 'scale', 'shape', 'order'])
+
+
class Scatter(PointsBase, ColormapMixIn, ScatterVisualizationMixIn):
"""Description of a scatter"""
@@ -87,7 +272,10 @@ class Scatter(PointsBase, ColormapMixIn, ScatterVisualizationMixIn):
_SUPPORTED_SCATTER_VISUALIZATION = (
ScatterVisualizationMixIn.Visualization.POINTS,
- ScatterVisualizationMixIn.Visualization.SOLID)
+ ScatterVisualizationMixIn.Visualization.SOLID,
+ ScatterVisualizationMixIn.Visualization.REGULAR_GRID,
+ ScatterVisualizationMixIn.Visualization.IRREGULAR_GRID,
+ )
"""Overrides supported Visualizations"""
def __init__(self):
@@ -104,7 +292,86 @@ class Scatter(PointsBase, ColormapMixIn, ScatterVisualizationMixIn):
# Cache triangles: x, y, indices
self.__cacheTriangles = None, None, None
+
+ # Cache regular grid info
+ self.__cacheRegularGridInfo = None
+
+ @docstring(ScatterVisualizationMixIn)
+ def setVisualizationParameter(self, parameter, value):
+ changed = super(Scatter, self).setVisualizationParameter(parameter, value)
+ if changed and parameter in (self.VisualizationParameter.GRID_BOUNDS,
+ self.VisualizationParameter.GRID_MAJOR_ORDER,
+ self.VisualizationParameter.GRID_SHAPE):
+ self.__cacheRegularGridInfo = None
+ return changed
+
+ @docstring(ScatterVisualizationMixIn)
+ def getCurrentVisualizationParameter(self, parameter):
+ value = self.getVisualizationParameter(parameter)
+ if value is not None:
+ return value # Value has been set, return it
+
+ elif parameter is self.VisualizationParameter.GRID_BOUNDS:
+ grid = self.__getRegularGridInfo()
+ return None if grid is None else grid.bounds
+ elif parameter is self.VisualizationParameter.GRID_MAJOR_ORDER:
+ grid = self.__getRegularGridInfo()
+ return None if grid is None else grid.order
+
+ elif parameter is self.VisualizationParameter.GRID_SHAPE:
+ grid = self.__getRegularGridInfo()
+ return None if grid is None else grid.shape
+
+ else:
+ raise NotImplementedError()
+
+ def __getRegularGridInfo(self):
+ """Get grid info"""
+ if self.__cacheRegularGridInfo is None:
+ shape = self.getVisualizationParameter(
+ self.VisualizationParameter.GRID_SHAPE)
+ order = self.getVisualizationParameter(
+ self.VisualizationParameter.GRID_MAJOR_ORDER)
+ if shape is None or order is None:
+ guess = _guess_grid(self.getXData(copy=False),
+ self.getYData(copy=False))
+ if guess is None:
+ _logger.warning(
+ 'Cannot guess a grid: Cannot display as regular grid image')
+ return None
+ if shape is None:
+ shape = guess[1]
+ if order is None:
+ order = guess[0]
+
+ bounds = self.getVisualizationParameter(
+ self.VisualizationParameter.GRID_BOUNDS)
+ if bounds is None:
+ x, y = self.getXData(copy=False), self.getYData(copy=False)
+ min_, max_ = min_max(x)
+ xRange = (min_, max_) if (x[0] - min_) < (max_ - x[0]) else (max_, min_)
+ min_, max_ = min_max(y)
+ yRange = (min_, max_) if (y[0] - min_) < (max_ - y[0]) else (max_, min_)
+ bounds = (xRange[0], yRange[0]), (xRange[1], yRange[1])
+
+ begin, end = bounds
+ scale = ((end[0] - begin[0]) / max(1, shape[1] - 1),
+ (end[1] - begin[1]) / max(1, shape[0] - 1))
+ if scale[0] == 0 and scale[1] == 0:
+ scale = 1., 1.
+ elif scale[0] == 0:
+ scale = scale[1], scale[1]
+ elif scale[1] == 0:
+ scale = scale[0], scale[0]
+
+ origin = begin[0] - 0.5 * scale[0], begin[1] - 0.5 * scale[1]
+
+ self.__cacheRegularGridInfo = _RegularGridInfo(
+ bounds=bounds, origin=origin, scale=scale, shape=shape, order=order)
+
+ return self.__cacheRegularGridInfo
+
def _addBackendRenderer(self, backend):
"""Update backend renderer"""
# Filter-out values <= 0
@@ -129,8 +396,10 @@ class Scatter(PointsBase, ColormapMixIn, ScatterVisualizationMixIn):
# Apply mask to colors
rgbacolors = rgbacolors[mask]
- if self.getVisualization() is self.Visualization.POINTS:
- return backend.addCurve(xFiltered, yFiltered, self.getLegend(),
+ visualization = self.getVisualization()
+
+ if visualization is self.Visualization.POINTS:
+ return backend.addCurve(xFiltered, yFiltered,
color=rgbacolors,
symbol=self.getSymbol(),
linewidth=0,
@@ -139,32 +408,153 @@ class Scatter(PointsBase, ColormapMixIn, ScatterVisualizationMixIn):
xerror=xerror,
yerror=yerror,
z=self.getZValue(),
- selectable=self.isSelectable(),
fill=False,
alpha=self.getAlpha(),
- symbolsize=self.getSymbolSize())
+ symbolsize=self.getSymbolSize(),
+ baseline=None)
- else: # 'solid'
+ else:
plot = self.getPlot()
if (plot is None or
plot.getXAxis().getScale() != Axis.LINEAR or
plot.getYAxis().getScale() != Axis.LINEAR):
- # Solid visualization is not available with log scaled axes
+ # Those visualizations are not available with log scaled axes
return None
- triangulation = self._getDelaunay().result()
- if triangulation is None:
- return None
- else:
- triangles = triangulation.simplices.astype(numpy.int32)
- return backend.addTriangles(xFiltered,
- yFiltered,
- triangles,
- legend=self.getLegend(),
- color=rgbacolors,
+ if visualization is self.Visualization.SOLID:
+ triangulation = self._getDelaunay().result()
+ if triangulation is None:
+ _logger.warning(
+ 'Cannot get a triangulation: Cannot display as solid surface')
+ return None
+ else:
+ triangles = triangulation.simplices.astype(numpy.int32)
+ return backend.addTriangles(xFiltered,
+ yFiltered,
+ triangles,
+ color=rgbacolors,
+ z=self.getZValue(),
+ alpha=self.getAlpha())
+
+ elif visualization is self.Visualization.REGULAR_GRID:
+ gridInfo = self.__getRegularGridInfo()
+ if gridInfo is None:
+ return None
+
+ dim0, dim1 = gridInfo.shape
+ if gridInfo.order == 'column': # transposition needed
+ dim0, dim1 = dim1, dim0
+
+ if len(rgbacolors) == dim0 * dim1:
+ image = rgbacolors.reshape(dim0, dim1, -1)
+ else:
+ # The points do not fill the whole image
+ image = numpy.empty((dim0 * dim1, 4), dtype=rgbacolors.dtype)
+ image[:len(rgbacolors)] = rgbacolors
+ image[len(rgbacolors):] = 0, 0, 0, 0 # Transparent pixels
+ image.shape = dim0, dim1, -1
+
+ if gridInfo.order == 'column':
+ image = numpy.transpose(image, axes=(1, 0, 2))
+
+ return backend.addImage(
+ data=image,
+ origin=gridInfo.origin,
+ scale=gridInfo.scale,
+ z=self.getZValue(),
+ colormap=None,
+ alpha=self.getAlpha())
+
+ elif visualization is self.Visualization.IRREGULAR_GRID:
+ gridInfo = self.__getRegularGridInfo()
+ if gridInfo is None:
+ return None
+
+ shape = gridInfo.shape
+ if shape is None: # No shape, no display
+ return None
+
+ # clip shape to fully filled lines
+ if len(xFiltered) != numpy.prod(shape):
+ if gridInfo.order == 'row':
+ shape = len(xFiltered) // shape[1], shape[1]
+ else: # column-major order
+ shape = shape[0], len(xFiltered) // shape[0]
+ if shape[0] < 2 or shape[1] < 2: # Not enough points
+ return None
+
+ nbpoints = numpy.prod(shape)
+ if gridInfo.order == 'row':
+ points = numpy.transpose((xFiltered[:nbpoints], yFiltered[:nbpoints]))
+ points = points.reshape(shape[0], shape[1], 2)
+
+ else: # column-major order
+ points = numpy.transpose((yFiltered[:nbpoints], xFiltered[:nbpoints]))
+ points = points.reshape(shape[1], shape[0], 2)
+
+ coords, indices = _quadrilateral_grid_as_triangles(points)
+
+ if gridInfo.order == 'row':
+ x, y = coords[:, 0], coords[:, 1]
+ else: # column-major order
+ y, x = coords[:, 0], coords[:, 1]
+
+ gridcolors = numpy.empty(
+ (4 * nbpoints, rgbacolors.shape[-1]), dtype=rgbacolors.dtype)
+ for first in range(4):
+ gridcolors[first::4] = rgbacolors[:nbpoints]
+
+ return backend.addTriangles(x,
+ y,
+ indices,
+ color=gridcolors,
z=self.getZValue(),
- selectable=self.isSelectable(),
alpha=self.getAlpha())
+ else:
+ _logger.error("Unhandled visualization %s", visualization)
+ return None
+
+ @docstring(PointsBase)
+ def pick(self, x, y):
+ result = super(Scatter, self).pick(x, y)
+
+ if result is not None:
+ visualization = self.getVisualization()
+
+ if visualization is self.Visualization.IRREGULAR_GRID:
+ # Specific handling of picking for the irregular grid mode
+ index = result.getIndices(copy=False)[0] // 4
+ result = PickingResult(self, (index,))
+
+ elif visualization is self.Visualization.REGULAR_GRID:
+ # Specific handling of picking for the regular grid mode
+ plot = self.getPlot()
+ if plot is None:
+ return None
+
+ dataPos = plot.pixelToData(x, y)
+ if dataPos is None:
+ return None
+
+ gridInfo = self.__getRegularGridInfo()
+ if gridInfo is None:
+ return None
+
+ origin = gridInfo.origin
+ scale = gridInfo.scale
+ column = int((dataPos[0] - origin[0]) / scale[0])
+ row = int((dataPos[1] - origin[1]) / scale[1])
+
+ if gridInfo.order == 'row':
+ index = row * gridInfo.shape[1] + column
+ else:
+ index = row + column * gridInfo.shape[0]
+ if index >= len(self.getXData(copy=False)): # OK as long as not log scale
+ return None # Image can be larger than scatter
+
+ result = PickingResult(self, (index,))
+
+ return result
def __getExecutor(self):
"""Returns async greedy executor
@@ -358,6 +748,9 @@ class Scatter(PointsBase, ColormapMixIn, ScatterVisualizationMixIn):
self.__interpolatorFuture.cancel()
self.__interpolatorFuture = None
+ # Data changed, this needs update
+ self.__cacheRegularGridInfo = None
+
self._value = value
if alpha is not None:
diff --git a/silx/gui/plot/items/shape.py b/silx/gui/plot/items/shape.py
index 9fc1306..e6dc529 100644
--- a/silx/gui/plot/items/shape.py
+++ b/silx/gui/plot/items/shape.py
@@ -36,7 +36,7 @@ import numpy
import six
from ... import colors
-from .core import Item, ColorMixIn, FillMixIn, ItemChangedType, LineMixIn
+from .core import Item, ColorMixIn, FillMixIn, ItemChangedType, LineMixIn, YAxisMixIn
_logger = logging.getLogger(__name__)
@@ -70,7 +70,6 @@ class Shape(Item, ColorMixIn, FillMixIn, LineMixIn):
x, y = points.T[0], points.T[1]
return backend.addItem(x,
y,
- legend=self.getLegend(),
shape=self.getType(),
color=self.getColor(),
fill=self.isFill(),
@@ -154,3 +153,64 @@ class Shape(Item, ColorMixIn, FillMixIn, LineMixIn):
self._lineBgColor = color
self._updated(ItemChangedType.LINE_BG_COLOR)
+
+
+class BoundingRect(Item, YAxisMixIn):
+ """An invisible shape which enforce the plot view to display the defined
+ space on autoscale.
+
+ This item do not display anything. But if the visible property is true,
+ this bounding box is used by the plot, if not, the bounding box is
+ ignored. That's the default behaviour for plot items.
+
+ It can be applied on the "left" or "right" axes. Not both at the same time.
+ """
+
+ def __init__(self):
+ Item.__init__(self)
+ YAxisMixIn.__init__(self)
+ self.__bounds = None
+
+ def _updated(self, event=None, checkVisibility=True):
+ if event in (ItemChangedType.YAXIS,
+ ItemChangedType.VISIBLE,
+ ItemChangedType.DATA):
+ # TODO hackish data range implementation
+ plot = self.getPlot()
+ if plot is not None:
+ plot._invalidateDataRange()
+
+ super(BoundingRect, self)._updated(event, checkVisibility)
+
+ def setBounds(self, rect):
+ """Set the bounding box of this item in data coordinates
+
+ :param Union[None,List[float]] rect: (xmin, xmax, ymin, ymax) or None
+ """
+ if rect is not None:
+ rect = float(rect[0]), float(rect[1]), float(rect[2]), float(rect[3])
+ assert rect[0] <= rect[1]
+ assert rect[2] <= rect[3]
+
+ if rect != self.__bounds:
+ self.__bounds = rect
+ self._updated(ItemChangedType.DATA)
+
+ def _getBounds(self):
+ plot = self.getPlot()
+ if plot is not None:
+ xPositive = plot.getXAxis()._isLogarithmic()
+ yPositive = plot.getYAxis()._isLogarithmic()
+ if xPositive or yPositive:
+ bounds = list(self.__bounds)
+ if xPositive and bounds[1] <= 0:
+ return None
+ if xPositive and bounds[0] <= 0:
+ bounds[0] = bounds[1]
+ if yPositive and bounds[3] <= 0:
+ return None
+ if yPositive and bounds[2] <= 0:
+ bounds[2] = bounds[3]
+ return tuple(bounds)
+
+ return self.__bounds
diff --git a/silx/gui/plot/test/testPlotWidget.py b/silx/gui/plot/test/testPlotWidget.py
index 7449c12..9724ec6 100644..100755
--- a/silx/gui/plot/test/testPlotWidget.py
+++ b/silx/gui/plot/test/testPlotWidget.py
@@ -32,6 +32,7 @@ __date__ = "03/01/2019"
import unittest
import logging
import numpy
+import sys
from silx.utils.testutils import ParametricTestCase, parameterize
from silx.gui.utils.testutils import SignalListener
@@ -42,6 +43,7 @@ from silx.test.utils import test_options
from silx.gui import qt
from silx.gui.plot import PlotWidget
from silx.gui.plot.items.curve import CurveStyle
+from silx.gui.plot.items.shape import BoundingRect
from silx.gui.colors import Colormap
from .utils import PlotWidgetTestCase
@@ -57,6 +59,19 @@ DATA_2D = numpy.arange(SIZE ** 2).reshape(SIZE, SIZE)
logger = logging.getLogger(__name__)
+class TestSpecialBackend(PlotWidgetTestCase, ParametricTestCase):
+
+ def __init__(self, methodName='runTest', backend=None):
+ TestCaseQt.__init__(self, methodName=methodName)
+ self.__backend = backend
+
+ def _createPlot(self):
+ return PlotWidget(backend=self.__backend)
+
+ def testPlot(self):
+ self.assertIsNotNone(self.plot)
+
+
class TestPlotWidget(PlotWidgetTestCase, ParametricTestCase):
"""Basic tests for PlotWidget"""
@@ -475,6 +490,56 @@ class TestPlotCurve(PlotWidgetTestCase):
replace=False, resetzoom=False,
color=color, symbol='o')
+ def testPlotBaselineNumpyArray(self):
+ """simple test of the API with baseline as a numpy array"""
+ x = numpy.arange(0, 10, step=0.1)
+ my_sin = numpy.sin(x)
+ y = numpy.arange(-4, 6, step=0.1) + my_sin
+ baseline = y - 1.0
+
+ self.plot.addCurve(x=x, y=y, color='grey', legend='curve1', fill=True,
+ baseline=baseline)
+
+ def testPlotBaselineScalar(self):
+ """simple test of the API with baseline as an int"""
+ x = numpy.arange(0, 10, step=0.1)
+ my_sin = numpy.sin(x)
+ y = numpy.arange(-4, 6, step=0.1) + my_sin
+
+ self.plot.addCurve(x=x, y=y, color='grey', legend='curve1', fill=True,
+ baseline=0)
+
+ def testPlotBaselineList(self):
+ """simple test of the API with baseline as an int"""
+ x = numpy.arange(0, 10, step=0.1)
+ my_sin = numpy.sin(x)
+ y = numpy.arange(-4, 6, step=0.1) + my_sin
+
+ self.plot.addCurve(x=x, y=y, color='grey', legend='curve1', fill=True,
+ baseline=list(range(0, 100, 1)))
+
+
+class TestPlotHistogram(PlotWidgetTestCase):
+ """Basic tests for add Histogram"""
+ def setUp(self):
+ super(TestPlotHistogram, self).setUp()
+ self.edges = numpy.arange(0, 10, step=1)
+ self.histogram = numpy.random.random(len(self.edges))
+
+ def testPlot(self):
+ self.plot.addHistogram(histogram=self.histogram,
+ edges=self.edges,
+ legend='histogram1')
+
+ def testPlotBaseline(self):
+ self.plot.addHistogram(histogram=self.histogram,
+ edges=self.edges,
+ legend='histogram1',
+ color='blue',
+ baseline=-2,
+ z=2,
+ fill=True)
+
class TestPlotScatter(PlotWidgetTestCase, ParametricTestCase):
"""Basic tests for addScatter"""
@@ -487,7 +552,7 @@ class TestPlotScatter(PlotWidgetTestCase, ParametricTestCase):
self.plot.resetZoom()
def testScatterVisualization(self):
- self.plot.addScatter((0, 1, 2, 3), (2, 0, 2, 1), (0, 1, 2, 3))
+ self.plot.addScatter((0, 1, 0, 1), (0, 0, 2, 2), (0, 1, 2, 3))
self.plot.resetZoom()
self.qapp.processEvents()
@@ -495,12 +560,76 @@ class TestPlotScatter(PlotWidgetTestCase, ParametricTestCase):
for visualization in ('solid',
'points',
+ 'regular_grid',
scatter.Visualization.SOLID,
- scatter.Visualization.POINTS):
+ scatter.Visualization.POINTS,
+ scatter.Visualization.REGULAR_GRID):
with self.subTest(visualization=visualization):
scatter.setVisualization(visualization)
self.qapp.processEvents()
+ def testGridVisualization(self):
+ """Test regular and irregular grid mode with different points"""
+ points = { # name: (x, y, order)
+ 'single point': ((1.,), (1.,), 'row'),
+ 'horizontal line': ((0, 1, 2), (0, 0, 0), 'row'),
+ 'horizontal line backward': ((2, 1, 0), (0, 0, 0), 'row'),
+ 'vertical line': ((0, 0, 0), (0, 1, 2), 'row'),
+ 'vertical line backward': ((0, 0, 0), (2, 1, 0), 'row'),
+ 'grid fast x, +x +y': ((0, 1, 2, 0, 1, 2), (0, 0, 0, 1, 1, 1), 'row'),
+ 'grid fast x, +x -y': ((0, 1, 2, 0, 1, 2), (1, 1, 1, 0, 0, 0), 'row'),
+ 'grid fast x, -x -y': ((2, 1, 0, 2, 1, 0), (1, 1, 1, 0, 0, 0), 'row'),
+ 'grid fast x, -x +y': ((2, 1, 0, 2, 1, 0), (0, 0, 0, 1, 1, 1), 'row'),
+ 'grid fast y, +x +y': ((0, 0, 0, 1, 1, 1), (0, 1, 2, 0, 1, 2), 'column'),
+ 'grid fast y, +x -y': ((0, 0, 0, 1, 1, 1), (2, 1, 0, 2, 1, 0), 'column'),
+ 'grid fast y, -x -y': ((1, 1, 1, 0, 0, 0), (2, 1, 0, 2, 1, 0), 'column'),
+ 'grid fast y, -x +y': ((1, 1, 1, 0, 0, 0), (0, 1, 2, 0, 1, 2), 'column'),
+ }
+
+ self.plot.addScatter((), (), ())
+ scatter = self.plot.getItems()[0]
+
+ self.qapp.processEvents()
+
+ for visualization in (scatter.Visualization.REGULAR_GRID,
+ scatter.Visualization.IRREGULAR_GRID):
+ scatter.setVisualization(visualization)
+ self.assertIs(scatter.getVisualization(), visualization)
+
+ for name, (x, y, ref_order) in points.items():
+ with self.subTest(name=name, visualization=visualization.name):
+ scatter.setData(x, y, numpy.arange(len(x)))
+ self.plot.setGraphTitle(name)
+ self.plot.resetZoom()
+ self.qapp.processEvents()
+
+ order = scatter.getCurrentVisualizationParameter(
+ scatter.VisualizationParameter.GRID_MAJOR_ORDER)
+ self.assertEqual(ref_order, order)
+
+ ref_bounds = (x[0], y[0]), (x[-1], y[-1])
+ bounds = scatter.getCurrentVisualizationParameter(
+ scatter.VisualizationParameter.GRID_BOUNDS)
+ self.assertEqual(ref_bounds, bounds)
+
+ shape = scatter.getCurrentVisualizationParameter(
+ scatter.VisualizationParameter.GRID_SHAPE)
+
+ self.plot.getXAxis().setLimits(numpy.min(x) - 1, numpy.max(x) + 1)
+ self.plot.getYAxis().setLimits(numpy.min(y) - 1, numpy.max(y) + 1)
+ self.qapp.processEvents()
+
+ for index, position in enumerate(zip(x, y)):
+ xpixel, ypixel = self.plot.dataToPixel(*position)
+ result = scatter.pick(xpixel, ypixel)
+ if (visualization is scatter.Visualization.IRREGULAR_GRID and
+ (shape[0] < 2 or shape[1] < 2)):
+ self.assertIsNone(result)
+ else:
+ self.assertIsNotNone(result)
+ self.assertIs(result.getItem(), scatter)
+ self.assertEqual(result.getIndices()[0], (index,))
+
class TestPlotMarker(PlotWidgetTestCase):
"""Basic tests for add*Marker"""
@@ -593,6 +722,39 @@ class TestPlotMarker(PlotWidgetTestCase):
self.plot.resetZoom()
+ def testPlotMarkerYAxis(self):
+ # Check only the API
+
+ legend = self.plot.addMarker(10, 10)
+ item = self.plot._getMarker(legend)
+ self.assertEqual(item.getYAxis(), "left")
+
+ legend = self.plot.addMarker(10, 10, yaxis="right")
+ item = self.plot._getMarker(legend)
+ self.assertEqual(item.getYAxis(), "right")
+
+ legend = self.plot.addMarker(10, 10, yaxis="left")
+ item = self.plot._getMarker(legend)
+ self.assertEqual(item.getYAxis(), "left")
+
+ legend = self.plot.addXMarker(10, yaxis="right")
+ item = self.plot._getMarker(legend)
+ self.assertEqual(item.getYAxis(), "right")
+
+ legend = self.plot.addXMarker(10, yaxis="left")
+ item = self.plot._getMarker(legend)
+ self.assertEqual(item.getYAxis(), "left")
+
+ legend = self.plot.addYMarker(10, yaxis="right")
+ item = self.plot._getMarker(legend)
+ self.assertEqual(item.getYAxis(), "right")
+
+ legend = self.plot.addYMarker(10, yaxis="left")
+ item = self.plot._getMarker(legend)
+ self.assertEqual(item.getYAxis(), "left")
+
+ self.plot.resetZoom()
+
# TestPlotItem ################################################################
@@ -1185,6 +1347,53 @@ class TestPlotAxes(TestCaseQt, ParametricTestCase):
"""Test coverage on setAxesDisplayed(True)"""
self.plot.setAxesDisplayed(True)
+ def testBoundingRectItem(self):
+ item = BoundingRect()
+ item.setBounds((-1000, 1000, -2000, 2000))
+ self.plot._add(item)
+ self.plot.resetZoom()
+ limits = numpy.array(self.plot.getXAxis().getLimits())
+ numpy.testing.assert_almost_equal(limits, numpy.array([-1000, 1000]))
+ limits = numpy.array(self.plot.getYAxis().getLimits())
+ numpy.testing.assert_almost_equal(limits, numpy.array([-2000, 2000]))
+
+ def testBoundingRectRightItem(self):
+ item = BoundingRect()
+ item.setYAxis("right")
+ item.setBounds((-1000, 1000, -2000, 2000))
+ self.plot._add(item)
+ self.plot.resetZoom()
+ limits = numpy.array(self.plot.getXAxis().getLimits())
+ numpy.testing.assert_almost_equal(limits, numpy.array([-1000, 1000]))
+ limits = numpy.array(self.plot.getYAxis("right").getLimits())
+ numpy.testing.assert_almost_equal(limits, numpy.array([-2000, 2000]))
+
+ def testBoundingRectArguments(self):
+ item = BoundingRect()
+ with self.assertRaises(Exception):
+ item.setBounds((1000, -1000, -2000, 2000))
+ with self.assertRaises(Exception):
+ item.setBounds((-1000, 1000, 2000, -2000))
+
+ def testBoundingRectWithLog(self):
+ item = BoundingRect()
+ self.plot._add(item)
+
+ item.setBounds((-1000, 1000, -2000, 2000))
+ self.plot.getXAxis()._setLogarithmic(True)
+ self.plot.getYAxis()._setLogarithmic(False)
+ self.assertEqual(item.getBounds(), (1000, 1000, -2000, 2000))
+
+ item.setBounds((-1000, 1000, -2000, 2000))
+ self.plot.getXAxis()._setLogarithmic(False)
+ self.plot.getYAxis()._setLogarithmic(True)
+ self.assertEqual(item.getBounds(), (-1000, 1000, 2000, 2000))
+
+ item.setBounds((-1000, 0, -2000, 2000))
+ self.plot.getXAxis()._setLogarithmic(True)
+ self.plot.getYAxis()._setLogarithmic(False)
+ self.assertIsNone(item.getBounds())
+
class TestPlotCurveLog(PlotWidgetTestCase, ParametricTestCase):
"""Basic tests for addCurve with log scale axes"""
@@ -1564,6 +1773,7 @@ def suite():
testClasses = (TestPlotWidget,
TestPlotImage,
TestPlotCurve,
+ TestPlotHistogram,
TestPlotScatter,
TestPlotMarker,
TestPlotItem,
@@ -1581,6 +1791,10 @@ def suite():
for testClass in testClasses:
test_suite.addTest(parameterize(testClass, backend=None))
+ test_suite.addTest(parameterize(TestSpecialBackend, backend=u"mpl"))
+ if sys.version_info[0] == 2:
+ test_suite.addTest(parameterize(TestSpecialBackend, backend=b"mpl"))
+
if test_options.WITH_GL_TEST:
# Tests with OpenGL backend
for testClass in testClasses:
diff --git a/silx/gui/plot/test/testStats.py b/silx/gui/plot/test/testStats.py
index 4bc2144..185e79c 100644
--- a/silx/gui/plot/test/testStats.py
+++ b/silx/gui/plot/test/testStats.py
@@ -273,11 +273,12 @@ class TestStatsFormatter(TestCaseQt):
formatter.format(self.stat.calculate(self.curveContext)), '0.000')
-class TestStatsHandler(unittest.TestCase):
+class TestStatsHandler(TestCaseQt):
"""Make sure the StatHandler is correctly making the link between
:class:`StatBase` and :class:`StatFormatter` and checking the API is valid
"""
def setUp(self):
+ TestCaseQt.setUp(self)
self.plot1d = Plot1D()
x = range(20)
y = range(20)
@@ -289,6 +290,8 @@ class TestStatsHandler(unittest.TestCase):
def tearDown(self):
self.plot1d.setAttribute(qt.Qt.WA_DeleteOnClose)
self.plot1d.close()
+ self.plot1d = None
+ TestCaseQt.tearDown(self)
def testConstructor(self):
"""Make sure the constructor can deal will all possible arguments:
diff --git a/silx/gui/plot/tools/PositionInfo.py b/silx/gui/plot/tools/PositionInfo.py
index 83b61bd..fef11dd 100644
--- a/silx/gui/plot/tools/PositionInfo.py
+++ b/silx/gui/plot/tools/PositionInfo.py
@@ -121,7 +121,7 @@ class PositionInfo(qt.QWidget):
contentWidget.setText('------')
contentWidget.setTextInteractionFlags(qt.Qt.TextSelectableByMouse)
contentWidget.setFixedWidth(
- contentWidget.fontMetrics().width('##############'))
+ contentWidget.fontMetrics().boundingRect('##############').width())
layout.addWidget(contentWidget)
self._fields.append((contentWidget, name, func))
diff --git a/silx/gui/plot/tools/profile/_BaseProfileToolBar.py b/silx/gui/plot/tools/profile/_BaseProfileToolBar.py
index 6d9d6d4..ced81da 100644
--- a/silx/gui/plot/tools/profile/_BaseProfileToolBar.py
+++ b/silx/gui/plot/tools/profile/_BaseProfileToolBar.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2018 European Synchrotron Radiation Facility
+# Copyright (c) 2018-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -321,7 +321,7 @@ class _BaseProfileToolBar(qt.QToolBar):
def __roiAdded(self, roi):
"""Handle new ROI"""
- roi.setLabel('Profile')
+ roi.setName('Profile')
roi.setEditable(True)
# Remove any other ROI
diff --git a/silx/gui/plot/tools/roi.py b/silx/gui/plot/tools/roi.py
index eb933a0..3535097 100644
--- a/silx/gui/plot/tools/roi.py
+++ b/silx/gui/plot/tools/roi.py
@@ -253,7 +253,7 @@ class RegionOfInterestManager(qt.QObject):
else:
return False
- def _regionOfInterestChanged(self):
+ def _regionOfInterestChanged(self, event=None):
"""Handle ROI object changed"""
self.sigRoiChanged.emit()
@@ -271,7 +271,7 @@ class RegionOfInterestManager(qt.QObject):
number of ROIs has been reached.
"""
roi = roiClass(parent=None)
- roi.setLabel(str(label))
+ roi.setName(str(label))
roi.setFirstShapePoints(points)
self.addRoi(roi, index)
@@ -283,6 +283,9 @@ class RegionOfInterestManager(qt.QObject):
:param roi_items.RegionOfInterest roi: The ROI to add
:param int index: The position where to insert the ROI,
By default it is appended to the end of the list of ROIs
+ :param bool useManagerColor:
+ Whether to set the ROI color to the default one of the manager or not.
+ (Default: True).
:raise RuntimeError: When ROI cannot be added because the maximum
number of ROIs has been reached.
"""
@@ -297,6 +300,7 @@ class RegionOfInterestManager(qt.QObject):
roi.setColor(self.getColor())
roi.sigRegionChanged.connect(self._regionOfInterestChanged)
+ roi.sigItemChanged.connect(self._regionOfInterestChanged)
if index is None:
self._rois.append(roi)
@@ -321,6 +325,7 @@ class RegionOfInterestManager(qt.QObject):
self._rois.remove(roi)
roi.sigRegionChanged.disconnect(self._regionOfInterestChanged)
+ roi.sigItemChanged.disconnect(self._regionOfInterestChanged)
roi.setParent(None)
self._roisUpdated()
@@ -820,11 +825,14 @@ class RegionOfInterestTableWidget(qt.QTableWidget):
return
if column == 0:
- roi.setVisible(item.checkState() == qt.Qt.Checked)
- roi.setLabel(item.text())
+ # First collect information from item, then update ROI
+ # Otherwise, this causes issues issues
+ checked = item.checkState() == qt.Qt.Checked
+ text= item.text()
+ roi.setVisible(checked)
+ roi.setName(text)
elif column == 1:
- roi.setEditable(
- item.checkState() == qt.Qt.Checked)
+ roi.setEditable(item.checkState() == qt.Qt.Checked)
elif column in (2, 3, 4):
pass # TODO
else:
@@ -888,7 +896,7 @@ class RegionOfInterestTableWidget(qt.QTableWidget):
baseFlags = qt.Qt.ItemIsSelectable | qt.Qt.ItemIsEnabled
# Label and visible
- label = roi.getLabel()
+ label = roi.getName()
item = qt.QTableWidgetItem(label)
item.setFlags(baseFlags | qt.Qt.ItemIsEditable | qt.Qt.ItemIsUserCheckable)
item.setData(qt.Qt.UserRole, index)
diff --git a/silx/gui/plot3d/_model/items.py b/silx/gui/plot3d/_model/items.py
index 9fe3e51..7f3921a 100644
--- a/silx/gui/plot3d/_model/items.py
+++ b/silx/gui/plot3d/_model/items.py
@@ -45,7 +45,7 @@ from ...utils.image import convertArrayToQImage
from ...colors import preferredColormaps
from ... import qt, icons
from .. import items
-from ..items.volume import Isosurface, CutPlane
+from ..items.volume import Isosurface, CutPlane, ComplexIsosurface
from ..Plot3DWidget import Plot3DWidget
@@ -867,6 +867,17 @@ class ColormapRow(_ColormapBaseProxyRow):
self._sigColormapChanged.connect(self._updateColormapImage)
+ def getColormapImage(self):
+ """Returns image representing the colormap or None
+
+ :rtype: Union[QImage,None]
+ """
+ if self._colormapImage is None and self._colormap is not None:
+ image = numpy.zeros((16, 130, 3), dtype=numpy.uint8)
+ image[1:-1, 1:-1] = self._colormap.getNColors(image.shape[1] - 2)[:, :3]
+ self._colormapImage = convertArrayToQImage(image)
+ return self._colormapImage
+
def _get(self):
"""Getter for ProxyRow subclass"""
return None
@@ -908,13 +919,9 @@ class ColormapRow(_ColormapBaseProxyRow):
def data(self, column, role):
if column == 1 and role == qt.Qt.DecorationRole:
- if self._colormapImage is None:
- image = numpy.zeros((16, 130, 3), dtype=numpy.uint8)
- image[1:-1, 1:-1] = self._colormap.getNColors(image.shape[1] - 2)[:, :3]
- self._colormapImage = convertArrayToQImage(image)
- return self._colormapImage
-
- return super(ColormapRow, self).data(column, role)
+ return self.getColormapImage()
+ else:
+ return super(ColormapRow, self).data(column, role)
class SymbolRow(ItemProxyRow):
@@ -1055,12 +1062,12 @@ class ComplexModeRow(ItemProxyRow):
:param Item3D item: Scene item with symbol property
"""
- def __init__(self, item):
+ def __init__(self, item, name='Mode'):
names = [m.value.replace('_', ' ').title()
for m in item.supportedComplexModes()]
super(ComplexModeRow, self).__init__(
item=item,
- name='Mode',
+ name=name,
fget=item.getComplexMode,
fset=item.setComplexMode,
events=items.ItemChangedType.COMPLEX_MODE,
@@ -1283,6 +1290,71 @@ class IsosurfaceRow(Item3DRow):
return super(IsosurfaceRow, self).setData(column, value, role)
+class ComplexIsosurfaceRow(IsosurfaceRow):
+ """Represents an :class:`ComplexIsosurface` item.
+
+ :param ComplexIsosurface item:
+ """
+
+ _EVENTS = (items.ItemChangedType.VISIBLE,
+ items.ItemChangedType.COLOR,
+ items.ItemChangedType.COMPLEX_MODE)
+ """Events for which to update the first column in the tree"""
+
+ def __init__(self, item):
+ super(ComplexIsosurfaceRow, self).__init__(item)
+
+ self.addRow(ComplexModeRow(item, "Color Complex Mode"), index=1)
+ for row in self.children():
+ if isinstance(row, ColorProxyRow):
+ self._colorRow = row
+ break
+ else:
+ raise RuntimeError("Cannot retrieve Color tree row")
+ self._colormapRow = ColormapRow(item)
+
+ self.__updateRowsForItem(item)
+ item.sigItemChanged.connect(self.__itemChanged)
+
+ def __itemChanged(self, event):
+ """Update enabled/disabled rows"""
+ if event == items.ItemChangedType.COMPLEX_MODE:
+ item = self.sender()
+ self.__updateRowsForItem(item)
+
+ def __updateRowsForItem(self, item):
+ """Update rows for item
+
+ :param item:
+ """
+ if not isinstance(item, ComplexIsosurface):
+ return
+
+ if item.getComplexMode() == items.ComplexMixIn.ComplexMode.NONE:
+ removed = self._colormapRow
+ added = self._colorRow
+ else:
+ removed = self._colorRow
+ added = self._colormapRow
+
+ # Remove unwanted rows
+ if removed in self.children():
+ self.removeRow(removed)
+
+ # Add required rows
+ if added not in self.children():
+ self.addRow(added, index=2)
+
+ def data(self, column, role):
+ if column == 0 and role == qt.Qt.DecorationRole:
+ item = self.item()
+ if (item is not None and
+ item.getComplexMode() != items.ComplexMixIn.ComplexMode.NONE):
+ return self._colormapRow.getColormapImage()
+
+ return super(ComplexIsosurfaceRow, self).data(column, role)
+
+
class AddIsosurfaceRow(BaseRow):
"""Class for Isosurface create button
@@ -1358,7 +1430,7 @@ class VolumeIsoSurfacesRow(StaticRow):
volume.sigIsosurfaceRemoved.connect(self._isosurfaceRemoved)
if isinstance(volume, items.ComplexMixIn):
- self.addRow(ComplexModeRow(volume))
+ self.addRow(ComplexModeRow(volume, "Complex Mode"))
for item in volume.getIsosurfaces():
self.addRow(nodeFromItem(item))
@@ -1581,6 +1653,8 @@ def nodeFromItem(item):
# Item with specific model row class
if isinstance(item, (items.GroupItem, items.GroupWithAxesItem)):
return GroupItemRow(item)
+ elif isinstance(item, ComplexIsosurface):
+ return ComplexIsosurfaceRow(item)
elif isinstance(item, Isosurface):
return IsosurfaceRow(item)
diff --git a/silx/gui/plot3d/items/_pick.py b/silx/gui/plot3d/items/_pick.py
index b35ef0d..8494723 100644
--- a/silx/gui/plot3d/items/_pick.py
+++ b/silx/gui/plot3d/items/_pick.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2018 European Synchrotron Radiation Facility
+# Copyright (c) 2018-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -34,6 +34,7 @@ __date__ = "24/09/2018"
import logging
import numpy
+from ...plot.items._pick import PickingResult as _PickingResult
from ..scene import Viewport, Base
@@ -177,9 +178,8 @@ class PickContext(object):
return rayObject
-class PickingResult(object):
- """Class to access picking information in a 3D scene.
- """
+class PickingResult(_PickingResult):
+ """Class to access picking information in a 3D scene."""
def __init__(self, item, positions, indices=None, fetchdata=None):
"""Init
@@ -194,7 +194,8 @@ class PickingResult(object):
to provide an alternative function to access item data.
Default is to use `item.getData`.
"""
- self._item = item
+ super(PickingResult, self).__init__(item, indices)
+
self._objectPositions = numpy.array(
positions, copy=False, dtype=numpy.float)
@@ -205,36 +206,8 @@ class PickingResult(object):
self._scenePositions = None
self._ndcPositions = None
- if indices is None:
- self._indices = None
- else:
- self._indices = numpy.array(indices, copy=False, dtype=numpy.int)
-
self._fetchdata = fetchdata
- def getItem(self):
- """Returns the item this results corresponds to.
-
- :rtype: ~silx.gui.plot3d.items.Item3D
- """
- return self._item
-
- def getIndices(self, copy=True):
- """Returns indices of picked data.
-
- If data is 1D, it returns a numpy.ndarray, otherwise
- it returns a tuple with as many numpy.ndarray as there are
- dimensions in the data.
-
- :param bool copy: True (default) to get a copy,
- False to return internal arrays
- :rtype: Union[None,numpy.ndarray,List[numpy.ndarray]]
- """
- if self._indices is None:
- return None
- indices = numpy.array(self._indices, copy=copy)
- return indices if indices.ndim == 1 else tuple(indices)
-
def getData(self, copy=True):
"""Returns picked data values
diff --git a/silx/gui/plot3d/items/scatter.py b/silx/gui/plot3d/items/scatter.py
index e8ffee1..5fce629 100644
--- a/silx/gui/plot3d/items/scatter.py
+++ b/silx/gui/plot3d/items/scatter.py
@@ -234,6 +234,9 @@ class Scatter2D(DataItem3D, ColormapMixIn, SymbolMixIn,
}
"""Dict {visualization mode: property names used in this mode}"""
+ _SUPPORTED_SCATTER_VISUALIZATION = tuple(_VISUALIZATION_PROPERTIES.keys())
+ """Overrides supported Visualizations"""
+
def __init__(self, parent=None):
DataItem3D.__init__(self, parent=parent)
ColormapMixIn.__init__(self)
diff --git a/silx/gui/plot3d/items/volume.py b/silx/gui/plot3d/items/volume.py
index ae91e82..e0a2a1f 100644
--- a/silx/gui/plot3d/items/volume.py
+++ b/silx/gui/plot3d/items/volume.py
@@ -37,13 +37,14 @@ import numpy
from silx.math.combo import min_max
from silx.math.marchingcubes import MarchingCubes
+from silx.math.interpolate import interp3d
from ....utils.proxy import docstring
from ... import _glutils as glu
from ... import qt
from ...colors import rgba
-from ..scene import cutplane, primitives, transform, utils
+from ..scene import cutplane, function, primitives, transform, utils
from .core import BaseNodeItem, Item3D, ItemChangedType, Item3DChangedType
from .mixins import ColormapMixIn, ComplexMixIn, InterpolationMixIn, PlaneMixIn
@@ -301,6 +302,15 @@ class Isosurface(Item3D):
"""Return the color of this iso-surface (QColor)"""
return qt.QColor.fromRgbF(*self._color)
+ def _updateColor(self, color):
+ """Handle update of color
+
+ :param List[float] color: RGBA channels in [0, 1]
+ """
+ primitive = self._getScenePrimitive()
+ if len(primitive.children) != 0:
+ primitive.children[0].setAttribute('color', color)
+
def setColor(self, color):
"""Set the color of the iso-surface
@@ -310,15 +320,15 @@ class Isosurface(Item3D):
color = rgba(color)
if color != self._color:
self._color = color
- primitive = self._getScenePrimitive()
- if len(primitive.children) != 0:
- primitive.children[0].setAttribute('color', self._color)
+ self._updateColor(self._color)
self._updated(ItemChangedType.COLOR)
- def _updateScenePrimitive(self):
- """Update underlying mesh"""
- self._getScenePrimitive().children = []
+ def _computeIsosurface(self):
+ """Compute isosurface for current state.
+ :return: (vertices, normals, indices) arrays
+ :rtype: List[Union[None,numpy.ndarray]]
+ """
data = self.getData(copy=False)
if data is None:
@@ -349,24 +359,31 @@ class Isosurface(Item3D):
self._level = level
self._updated(Item3DChangedType.ISO_LEVEL)
- if not numpy.isfinite(self._level):
- return
+ if numpy.isfinite(self._level):
+ st = time.time()
+ vertices, normals, indices = MarchingCubes(
+ data,
+ isolevel=self._level)
+ _logger.info('Computed iso-surface in %f s.', time.time() - st)
- st = time.time()
- vertices, normals, indices = MarchingCubes(
- data,
- isolevel=self._level)
- _logger.info('Computed iso-surface in %f s.', time.time() - st)
+ if len(vertices) != 0:
+ return vertices, normals, indices
- if len(vertices) == 0:
- return
- else:
- mesh = primitives.Mesh3D(vertices,
- colors=self._color,
- normals=normals,
- mode='triangles',
- indices=indices)
- self._getScenePrimitive().children = [mesh]
+ return None, None, None
+
+ def _updateScenePrimitive(self):
+ """Update underlying mesh"""
+ self._getScenePrimitive().children = []
+
+ vertices, normals, indices = self._computeIsosurface()
+ if vertices is not None:
+ mesh = primitives.Mesh3D(vertices,
+ colors=self._color,
+ normals=normals,
+ mode='triangles',
+ indices=indices,
+ copy=False)
+ self._getScenePrimitive().children = [mesh]
def _pickFull(self, context):
"""Perform picking in this item at given widget position.
@@ -677,17 +694,39 @@ class ComplexCutPlane(CutPlane, ComplexMixIn):
super(ComplexCutPlane, self)._updated(event)
-class ComplexIsosurface(Isosurface):
+class ComplexIsosurface(Isosurface, ComplexMixIn, ColormapMixIn):
"""Class representing an iso-surface in a :class:`ComplexField3D` item.
:param parent: The DataItem3D this iso-surface belongs to
"""
+ _SUPPORTED_COMPLEX_MODES = \
+ (ComplexMixIn.ComplexMode.NONE,) + ComplexMixIn._SUPPORTED_COMPLEX_MODES
+ """Overrides supported ComplexMode"""
+
def __init__(self, parent):
- super(ComplexIsosurface, self).__init__(parent)
+ ComplexMixIn.__init__(self)
+ ColormapMixIn.__init__(self, function.Colormap())
+ Isosurface.__init__(self, parent=parent)
+ self.setComplexMode(self.ComplexMode.NONE)
+
+ def _updateColor(self, color):
+ """Handle update of color
+
+ :param List[float] color: RGBA channels in [0, 1]
+ """
+ primitive = self._getScenePrimitive()
+ if (len(primitive.children) != 0 and
+ isinstance(primitive.children[0], primitives.ColormapMesh3D)):
+ primitive.children[0].alpha = self._color[3]
+ else:
+ super(ComplexIsosurface, self)._updateColor(color)
def _syncDataWithParent(self):
"""Synchronize this instance data with that of its parent"""
+ if self.getComplexMode() != self.ComplexMode.NONE:
+ self._setRangeFromData(self.getColormappedData(copy=False))
+
parent = self.parent()
if parent is None:
self._data = None
@@ -702,6 +741,67 @@ class ComplexIsosurface(Isosurface):
self._syncDataWithParent()
super(ComplexIsosurface, self)._parentChanged(event)
+ def getColormappedData(self, copy=True):
+ """Return 3D dataset used to apply the colormap on the isosurface.
+
+ This depends on :meth:`getComplexMode`.
+
+ :param bool copy:
+ True (default) to get a copy,
+ False to get the internal data (DO NOT modify!)
+ :return: The data set (or None if not set)
+ :rtype: Union[numpy.ndarray,None]
+ """
+ if self.getComplexMode() == self.ComplexMode.NONE:
+ return None
+ else:
+ parent = self.parent()
+ if parent is None:
+ return None
+ else:
+ return parent.getData(mode=self.getComplexMode(), copy=copy)
+
+ def _updated(self, event=None):
+ """Handle update of the isosurface (and take care of mode change)
+
+ :param ItemChangedType event: The kind of update
+ """
+ if (event == ItemChangedType.COMPLEX_MODE and
+ self.getComplexMode() != self.ComplexMode.NONE):
+ self._setRangeFromData(self.getColormappedData(copy=False))
+
+ if event in (ItemChangedType.COMPLEX_MODE,
+ ItemChangedType.COLORMAP,
+ Item3DChangedType.INTERPOLATION):
+ self._updateScenePrimitive()
+ super(ComplexIsosurface, self)._updated(event)
+
+ def _updateScenePrimitive(self):
+ """Update underlying mesh"""
+ if self.getComplexMode() == self.ComplexMode.NONE:
+ super(ComplexIsosurface, self)._updateScenePrimitive()
+
+ else: # Specific display for colormapped isosurface
+ self._getScenePrimitive().children = []
+
+ values = self.getColormappedData(copy=False)
+ if values is not None:
+ vertices, normals, indices = self._computeIsosurface()
+ if vertices is not None:
+ values = interp3d(values, vertices, method='linear_omp')
+ # TODO reuse isosurface when only color changes...
+
+ mesh = primitives.ColormapMesh3D(
+ vertices,
+ value=values.reshape(-1, 1),
+ colormap=self._getSceneColormap(),
+ normal=normals,
+ mode='triangles',
+ indices=indices,
+ copy=False)
+ mesh.alpha = self._color[3]
+ self._getScenePrimitive().children = [mesh]
+
class ComplexField3D(ScalarField3D, ComplexMixIn):
"""3D complex field on a regular grid.
@@ -720,6 +820,7 @@ class ComplexField3D(ScalarField3D, ComplexMixIn):
@docstring(ComplexMixIn)
def setComplexMode(self, mode):
+ mode = ComplexMixIn.ComplexMode.from_value(mode)
if mode != self.getComplexMode():
self.clearIsosurfaces() # Reset isosurfaces
ComplexMixIn.setComplexMode(self, mode)
diff --git a/silx/gui/plot3d/scene/primitives.py b/silx/gui/plot3d/scene/primitives.py
index 08724ba..7db61e8 100644
--- a/silx/gui/plot3d/scene/primitives.py
+++ b/silx/gui/plot3d/scene/primitives.py
@@ -1874,6 +1874,8 @@ class ColormapMesh3D(Geometry):
}
""",
string.Template("""
+ uniform float alpha;
+
varying vec4 vCameraPosition;
varying vec3 vPosition;
varying vec3 vNormal;
@@ -1889,6 +1891,7 @@ class ColormapMesh3D(Geometry):
vec4 color = $colormapCall(vValue);
gl_FragColor = $lightingCall(color, vPosition, vNormal);
+ gl_FragColor.a *= alpha;
$scenePostCall(vCameraPosition);
}
@@ -1908,6 +1911,7 @@ class ColormapMesh3D(Geometry):
value=value,
copy=copy)
+ self._alpha = 1.0
self._lineWidth = 1.0
self._lineSmooth = True
self._culling = None
@@ -1922,6 +1926,10 @@ class ColormapMesh3D(Geometry):
converter=bool,
doc="Smooth line rendering enabled (bool, default: True)")
+ alpha = event.notifyProperty(
+ '_alpha', converter=float,
+ doc="Transparency of the mesh, float in [0, 1]")
+
@property
def culling(self):
"""Face culling (str)
@@ -1978,6 +1986,7 @@ class ColormapMesh3D(Geometry):
program.setUniformMatrix('transformMat',
ctx.objectToCamera.matrix,
safe=True)
+ gl.glUniform1f(program.uniforms['alpha'], self._alpha)
if self.drawMode in self._LINE_MODES:
gl.glLineWidth(self.lineWidth)
diff --git a/silx/gui/plot3d/tools/PositionInfoWidget.py b/silx/gui/plot3d/tools/PositionInfoWidget.py
index fc86a7f..52a6163 100644
--- a/silx/gui/plot3d/tools/PositionInfoWidget.py
+++ b/silx/gui/plot3d/tools/PositionInfoWidget.py
@@ -189,7 +189,7 @@ class PositionInfoWidget(qt.QWidget):
return # No picked item
item = picking.getItem()
- self._itemLabel.setText(item.getLabel())
+ self._itemLabel.setText(item.getName())
positions = picking.getPositions('scene', copy=False)
x, y, z = positions[0]
self._xLabel.setText("%g" % x)
diff --git a/silx/gui/qt/_utils.py b/silx/gui/qt/_utils.py
index 912f08c..f5915ae 100644
--- a/silx/gui/qt/_utils.py
+++ b/silx/gui/qt/_utils.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2004-2017 European Synchrotron Radiation Facility
+# Copyright (c) 2004-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -29,21 +29,22 @@ __authors__ = ["V. Valls"]
__license__ = "MIT"
__date__ = "30/11/2016"
-import sys
-from . import _qt as qt
+
+import sys as _sys
+from . import _qt
def supportedImageFormats():
"""Return a set of string of file format extensions supported by the
Qt runtime."""
- if sys.version_info[0] < 3 or qt.BINDING == 'PySide':
+ if _sys.version_info[0] < 3 or _qt.BINDING == 'PySide':
convert = str
- elif qt.BINDING == 'PySide2':
+ elif _qt.BINDING == 'PySide2':
def convert(data):
return str(data.data(), 'ascii')
else:
convert = lambda data: str(data, 'ascii')
- formats = qt.QImageReader.supportedImageFormats()
+ formats = _qt.QImageReader.supportedImageFormats()
return set([convert(data) for data in formats])
@@ -59,7 +60,7 @@ def silxGlobalThreadPool():
"""
global __globalThreadPoolInstance
if __globalThreadPoolInstance is None:
- tp = qt.QThreadPool()
+ tp = _qt.QThreadPool()
# This pointless command fixes a segfault with PyQt 5.9.1 on Windows
tp.setMaxThreadCount(tp.maxThreadCount())
__globalThreadPoolInstance = tp
diff --git a/silx/gui/test/test_colors.py b/silx/gui/test/test_colors.py
index 6e4fc73..12387a3 100644..100755
--- a/silx/gui/test/test_colors.py
+++ b/silx/gui/test/test_colors.py
@@ -39,28 +39,39 @@ from silx.gui.colors import Colormap
from silx.utils.exceptions import NotEditableError
-class TestRGBA(ParametricTestCase):
+class TestColor(ParametricTestCase):
"""Basic tests of rgba function"""
+ TEST_COLORS = { # name: (colors, expected values)
+ 'blue': ('blue', (0., 0., 1., 1.)),
+ '#010203': ('#010203', (1. / 255., 2. / 255., 3. / 255., 1.)),
+ '#01020304': ('#01020304', (1. / 255., 2. / 255., 3. / 255., 4. / 255.)),
+ '3 x uint8': (numpy.array((1, 255, 0), dtype=numpy.uint8),
+ (1 / 255., 1., 0., 1.)),
+ '4 x uint8': (numpy.array((1, 255, 0, 1), dtype=numpy.uint8),
+ (1 / 255., 1., 0., 1 / 255.)),
+ '3 x float overflow': ((3., 0.5, 1.), (1., 0.5, 1., 1.)),
+ }
+
def testRGBA(self):
""""Test rgba function with accepted values"""
- tests = { # name: (colors, expected values)
- 'blue': ('blue', (0., 0., 1., 1.)),
- '#010203': ('#010203', (1. / 255., 2. / 255., 3. / 255., 1.)),
- '#01020304': ('#01020304', (1. / 255., 2. / 255., 3. / 255., 4. / 255.)),
- '3 x uint8': (numpy.array((1, 255, 0), dtype=numpy.uint8),
- (1 / 255., 1., 0., 1.)),
- '4 x uint8': (numpy.array((1, 255, 0, 1), dtype=numpy.uint8),
- (1 / 255., 1., 0., 1 / 255.)),
- '3 x float overflow': ((3., 0.5, 1.), (1., 0.5, 1., 1.)),
- }
-
- for name, test in tests.items():
+ for name, test in self.TEST_COLORS.items():
color, expected = test
with self.subTest(msg=name):
result = colors.rgba(color)
self.assertEqual(result, expected)
+ def testQColor(self):
+ """"Test getQColor function with accepted values"""
+ for name, test in self.TEST_COLORS.items():
+ color, expected = test
+ with self.subTest(msg=name):
+ result = colors.asQColor(color)
+ self.assertAlmostEqual(result.redF(), expected[0], places=4)
+ self.assertAlmostEqual(result.greenF(), expected[1], places=4)
+ self.assertAlmostEqual(result.blueF(), expected[2], places=4)
+ self.assertAlmostEqual(result.alphaF(), expected[3], places=4)
+
class TestApplyColormapToData(ParametricTestCase):
"""Tests of applyColormapToData function"""
@@ -477,7 +488,7 @@ def suite():
test_suite = unittest.TestSuite()
loadTests = unittest.defaultTestLoader.loadTestsFromTestCase
test_suite.addTest(loadTests(TestApplyColormapToData))
- test_suite.addTest(loadTests(TestRGBA))
+ test_suite.addTest(loadTests(TestColor))
test_suite.addTest(loadTests(TestDictAPI))
test_suite.addTest(loadTests(TestObjectAPI))
test_suite.addTest(loadTests(TestPreferredColormaps))
diff --git a/silx/gui/utils/__init__.py b/silx/gui/utils/__init__.py
index 51c4fac..a4e442f 100644..100755
--- a/silx/gui/utils/__init__.py
+++ b/silx/gui/utils/__init__.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2018 European Synchrotron Radiation Facility
+# Copyright (c) 2018-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -27,3 +27,33 @@
__authors__ = ["T. Vincent"]
__license__ = "MIT"
__date__ = "09/03/2018"
+
+
+import contextlib as _contextlib
+
+
+@_contextlib.contextmanager
+def blockSignals(*objs):
+ """Context manager blocking signals of QObjects.
+
+ It restores previous state when leaving.
+
+ :param qt.QObject objs: QObjects for which to block signals
+ """
+ blocked = [(obj, obj.blockSignals(True)) for obj in objs]
+ try:
+ yield
+ finally:
+ for obj, previous in blocked:
+ obj.blockSignals(previous)
+
+
+def getQEventName(eventType):
+ """
+ Returns the name of a QEvent.
+
+ :param Union[int,qt.QEvent] eventType: A QEvent or a QEvent type.
+ :returns: str
+ """
+ from . import qtutils
+ return qtutils.getQEventName(eventType)
diff --git a/silx/gui/utils/qtutils.py b/silx/gui/utils/qtutils.py
new file mode 100755
index 0000000..eb823a8
--- /dev/null
+++ b/silx/gui/utils/qtutils.py
@@ -0,0 +1,170 @@
+from silx.gui import qt
+
+
+QT_EVENT_NAMES = {
+ 0: "None",
+ 114: "ActionAdded",
+ 113: "ActionChanged",
+ 115: "ActionRemoved",
+ 99: "ActivationChange",
+ 121: "ApplicationActivate",
+ # ApplicationActivate: "ApplicationActivated",
+ 122: "ApplicationDeactivate",
+ 36: "ApplicationFontChange",
+ 37: "ApplicationLayoutDirectionChange",
+ 38: "ApplicationPaletteChange",
+ 214: "ApplicationStateChange",
+ 35: "ApplicationWindowIconChange",
+ 68: "ChildAdded",
+ 69: "ChildPolished",
+ 71: "ChildRemoved",
+ 40: "Clipboard",
+ 19: "Close",
+ 200: "CloseSoftwareInputPanel",
+ 178: "ContentsRectChange",
+ 82: "ContextMenu",
+ 183: "CursorChange",
+ 52: "DeferredDelete",
+ 60: "DragEnter",
+ 62: "DragLeave",
+ 61: "DragMove",
+ 63: "Drop",
+ 170: "DynamicPropertyChange",
+ 98: "EnabledChange",
+ 10: "Enter",
+ 150: "EnterEditFocus",
+ 124: "EnterWhatsThisMode",
+ 206: "Expose",
+ 116: "FileOpen",
+ 8: "FocusIn",
+ 9: "FocusOut",
+ 23: "FocusAboutToChange",
+ 97: "FontChange",
+ 198: "Gesture",
+ 202: "GestureOverride",
+ 188: "GrabKeyboard",
+ 186: "GrabMouse",
+ 159: "GraphicsSceneContextMenu",
+ 164: "GraphicsSceneDragEnter",
+ 166: "GraphicsSceneDragLeave",
+ 165: "GraphicsSceneDragMove",
+ 167: "GraphicsSceneDrop",
+ 163: "GraphicsSceneHelp",
+ 160: "GraphicsSceneHoverEnter",
+ 162: "GraphicsSceneHoverLeave",
+ 161: "GraphicsSceneHoverMove",
+ 158: "GraphicsSceneMouseDoubleClick",
+ 155: "GraphicsSceneMouseMove",
+ 156: "GraphicsSceneMousePress",
+ 157: "GraphicsSceneMouseRelease",
+ 182: "GraphicsSceneMove",
+ 181: "GraphicsSceneResize",
+ 168: "GraphicsSceneWheel",
+ 18: "Hide",
+ 27: "HideToParent",
+ 127: "HoverEnter",
+ 128: "HoverLeave",
+ 129: "HoverMove",
+ 96: "IconDrag",
+ 101: "IconTextChange",
+ 83: "InputMethod",
+ 207: "InputMethodQuery",
+ 169: "KeyboardLayoutChange",
+ 6: "KeyPress",
+ 7: "KeyRelease",
+ 89: "LanguageChange",
+ 90: "LayoutDirectionChange",
+ 76: "LayoutRequest",
+ 11: "Leave",
+ 151: "LeaveEditFocus",
+ 125: "LeaveWhatsThisMode",
+ 88: "LocaleChange",
+ 176: "NonClientAreaMouseButtonDblClick",
+ 174: "NonClientAreaMouseButtonPress",
+ 175: "NonClientAreaMouseButtonRelease",
+ 173: "NonClientAreaMouseMove",
+ 177: "MacSizeChange",
+ 43: "MetaCall",
+ 102: "ModifiedChange",
+ 4: "MouseButtonDblClick",
+ 2: "MouseButtonPress",
+ 3: "MouseButtonRelease",
+ 5: "MouseMove",
+ 109: "MouseTrackingChange",
+ 13: "Move",
+ 197: "NativeGesture",
+ 208: "OrientationChange",
+ 12: "Paint",
+ 39: "PaletteChange",
+ 131: "ParentAboutToChange",
+ 21: "ParentChange",
+ 212: "PlatformPanel",
+ 217: "PlatformSurface",
+ 75: "Polish",
+ 74: "PolishRequest",
+ 123: "QueryWhatsThis",
+ 106: "ReadOnlyChange",
+ 199: "RequestSoftwareInputPanel",
+ 14: "Resize",
+ 204: "ScrollPrepare",
+ 205: "Scroll",
+ 117: "Shortcut",
+ 51: "ShortcutOverride",
+ 17: "Show",
+ 26: "ShowToParent",
+ 50: "SockAct",
+ 192: "StateMachineSignal",
+ 193: "StateMachineWrapped",
+ 112: "StatusTip",
+ 100: "StyleChange",
+ 87: "TabletMove",
+ 92: "TabletPress",
+ 93: "TabletRelease",
+ 171: "TabletEnterProximity",
+ 172: "TabletLeaveProximity",
+ 219: "TabletTrackingChange",
+ 22: "ThreadChange",
+ 1: "Timer",
+ 120: "ToolBarChange",
+ 110: "ToolTip",
+ 184: "ToolTipChange",
+ 194: "TouchBegin",
+ 209: "TouchCancel",
+ 196: "TouchEnd",
+ 195: "TouchUpdate",
+ 189: "UngrabKeyboard",
+ 187: "UngrabMouse",
+ 78: "UpdateLater",
+ 77: "UpdateRequest",
+ 111: "WhatsThis",
+ 118: "WhatsThisClicked",
+ 31: "Wheel",
+ 132: "WinEventAct",
+ 24: "WindowActivate",
+ 103: "WindowBlocked",
+ 25: "WindowDeactivate",
+ 34: "WindowIconChange",
+ 105: "WindowStateChange",
+ 33: "WindowTitleChange",
+ 104: "WindowUnblocked",
+ 203: "WinIdChange",
+ 126: "ZOrderChange",
+ 65535: "MaxUser",
+}
+
+
+def getQEventName(eventType):
+ """
+ Returns the name of a QEvent.
+
+ :param Union[int,qt.QEvent] eventType: A QEvent or a QEvent type.
+ :returns: str
+ """
+ if isinstance(eventType, qt.QEvent):
+ eventType = eventType.type()
+ if 1000 <= eventType <= 65535:
+ return "User_%d" % eventType
+ name = QT_EVENT_NAMES.get(eventType, None)
+ if name is not None:
+ return name
+ return "Unknown_%d" % eventType
diff --git a/silx/gui/utils/test/__init__.py b/silx/gui/utils/test/__init__.py
index 9e50170..d500c05 100644..100755
--- a/silx/gui/utils/test/__init__.py
+++ b/silx/gui/utils/test/__init__.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2018 European Synchrotron Radiation Facility
+# Copyright (c) 2018-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -34,15 +34,21 @@ import unittest
from . import test_async
from . import test_image
+from . import test_qtutils
+from . import test_testutils
+from . import test
def suite():
"""Test suite for module silx.image.test"""
test_suite = unittest.TestSuite()
+ test_suite.addTest(test.suite())
test_suite.addTest(test_async.suite())
test_suite.addTest(test_image.suite())
+ test_suite.addTest(test_qtutils.suite())
+ test_suite.addTest(test_testutils.suite())
return test_suite
-if __name__ == '__main__':
- unittest.main(defaultTest='suite')
+if __name__ == "__main__":
+ unittest.main(defaultTest="suite")
diff --git a/silx/gui/utils/test/test.py b/silx/gui/utils/test/test.py
new file mode 100644
index 0000000..8bba852
--- /dev/null
+++ b/silx/gui/utils/test/test.py
@@ -0,0 +1,76 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2019 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""Test of functions available in silx.gui.utils module."""
+
+from __future__ import absolute_import
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "01/08/2019"
+
+
+import unittest
+from silx.gui import qt
+from silx.gui.utils.testutils import TestCaseQt, SignalListener
+
+from silx.gui.utils import blockSignals
+
+
+class TestBlockSignals(TestCaseQt):
+ """Test blockSignals context manager"""
+
+ def _test(self, *objs):
+ """Test for provided objects"""
+ listener = SignalListener()
+ for obj in objs:
+ obj.objectNameChanged.connect(listener)
+ obj.setObjectName("received")
+
+ with blockSignals(*objs):
+ for obj in objs:
+ obj.setObjectName("silent")
+
+ self.assertEqual(listener.arguments(), [("received",)] * len(objs))
+
+ @unittest.skipUnless(qt.BINDING in ('PyQt5', 'PySide2'), 'Qt5 only test')
+ def testManyObjects(self):
+ """Test blockSignals with 2 QObjects"""
+ self._test(qt.QObject(), qt.QObject())
+
+ @unittest.skipUnless(qt.BINDING in ('PyQt5', 'PySide2'), 'Qt5 only test')
+ def testOneObject(self):
+ """Test blockSignals context manager with a single QObject"""
+ self._test(qt.QObject())
+
+
+def suite():
+ test_suite = unittest.TestSuite()
+ test_suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(
+ TestBlockSignals))
+ return test_suite
+
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='suite')
diff --git a/silx/gui/utils/test/test_qtutils.py b/silx/gui/utils/test/test_qtutils.py
new file mode 100755
index 0000000..043a0a6
--- /dev/null
+++ b/silx/gui/utils/test/test_qtutils.py
@@ -0,0 +1,75 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2019 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""Test of functions available in silx.gui.utils module."""
+
+from __future__ import absolute_import
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "01/08/2019"
+
+
+import unittest
+from silx.gui import qt
+from silx.gui import utils
+from silx.gui.utils.testutils import TestCaseQt
+
+
+class TestQEventName(TestCaseQt):
+ """Test QEvent names"""
+
+ def testNoneType(self):
+ result = utils.getQEventName(0)
+ self.assertEqual(result, "None")
+
+ def testNoneEvent(self):
+ event = qt.QEvent(qt.QEvent.Type(0))
+ result = utils.getQEventName(event)
+ self.assertEqual(result, "None")
+
+ def testUserType(self):
+ result = utils.getQEventName(1050)
+ self.assertIn("User", result)
+ self.assertIn("1050", result)
+
+ def testQtUndefinedType(self):
+ result = utils.getQEventName(900)
+ self.assertIn("Unknown", result)
+ self.assertIn("900", result)
+
+ def testUndefinedType(self):
+ result = utils.getQEventName(70000)
+ self.assertIn("Unknown", result)
+ self.assertIn("70000", result)
+
+
+def suite():
+ test_suite = unittest.TestSuite()
+ test_suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestQEventName))
+ return test_suite
+
+
+if __name__ == "__main__":
+ unittest.main(defaultTest="suite")
diff --git a/silx/gui/utils/test/test_testutils.py b/silx/gui/utils/test/test_testutils.py
new file mode 100644
index 0000000..8a58e6e
--- /dev/null
+++ b/silx/gui/utils/test/test_testutils.py
@@ -0,0 +1,55 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2017-2019 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""Test of testutils module."""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "16/01/2017"
+
+import unittest
+import sys
+
+from silx.gui import qt
+from ..testutils import TestCaseQt
+
+
+class TestOutcome(unittest.TestCase):
+ """Tests conversion of QImage to/from numpy array."""
+
+ @unittest.skipIf(sys.version_info.major <= 2, 'Python3 only')
+ def testNoneOutcome(self):
+ test = TestCaseQt()
+ test._currentTestSucceeded()
+
+
+def suite():
+ test_suite = unittest.TestSuite()
+ loader = unittest.defaultTestLoader.loadTestsFromTestCase
+ test_suite.addTest(loader(TestOutcome))
+ return test_suite
+
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='suite')
diff --git a/silx/gui/utils/testutils.py b/silx/gui/utils/testutils.py
index d7f2f41..14dcc3f 100644
--- a/silx/gui/utils/testutils.py
+++ b/silx/gui/utils/testutils.py
@@ -155,7 +155,8 @@ class TestCaseQt(unittest.TestCase):
if hasattr(self, '_outcome'):
# For Python >= 3.4
result = self.defaultTestResult() # these 2 methods have no side effects
- self._feedErrorsToResult(result, self._outcome.errors)
+ if hasattr(self._outcome, 'errors'):
+ self._feedErrorsToResult(result, self._outcome.errors)
else:
# For Python < 3.4
result = getattr(self, '_outcomeForDoCleanups', self._resultForDoCleanups)
diff --git a/silx/gui/widgets/ColormapNameComboBox.py b/silx/gui/widgets/ColormapNameComboBox.py
new file mode 100644
index 0000000..fa8faf1
--- /dev/null
+++ b/silx/gui/widgets/ColormapNameComboBox.py
@@ -0,0 +1,166 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2004-2018 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""A QComboBox to display prefered colormaps
+"""
+
+from __future__ import division
+
+__authors__ = ["V.A. Sole", "T. Vincent", "H. Payno"]
+__license__ = "MIT"
+__date__ = "27/11/2018"
+
+
+import logging
+import numpy
+
+from .. import qt
+from .. import colors as colors_mdl
+
+_logger = logging.getLogger(__name__)
+
+
+_colormapIconPreview = {}
+
+
+class ColormapNameComboBox(qt.QComboBox):
+ def __init__(self, parent=None):
+ qt.QComboBox.__init__(self, parent)
+ self.__initItems()
+
+ LUT_NAME = qt.Qt.UserRole + 1
+ LUT_COLORS = qt.Qt.UserRole + 2
+
+ def __initItems(self):
+ for colormapName in colors_mdl.preferredColormaps():
+ index = self.count()
+ self.addItem(str.title(colormapName))
+ self.setItemIcon(index, self.getIconPreview(name=colormapName))
+ self.setItemData(index, colormapName, role=self.LUT_NAME)
+
+ def getIconPreview(self, name=None, colors=None):
+ """Return an icon preview from a LUT name.
+
+ This icons are cached into a global structure.
+
+ :param str name: Name of the LUT
+ :param numpy.ndarray colors: Colors identify the LUT
+ :rtype: qt.QIcon
+ """
+ if name is not None:
+ iconKey = name
+ else:
+ iconKey = tuple(colors)
+ icon = _colormapIconPreview.get(iconKey, None)
+ if icon is None:
+ icon = self.createIconPreview(name, colors)
+ _colormapIconPreview[iconKey] = icon
+ return icon
+
+ def createIconPreview(self, name=None, colors=None):
+ """Create and return an icon preview from a LUT name.
+
+ This icons are cached into a global structure.
+
+ :param str name: Name of the LUT
+ :param numpy.ndarray colors: Colors identify the LUT
+ :rtype: qt.QIcon
+ """
+ colormap = colors_mdl.Colormap(name)
+ size = 32
+ if name is not None:
+ lut = colormap.getNColors(size)
+ else:
+ lut = colors
+ if len(lut) > size:
+ # Down sample
+ step = int(len(lut) / size)
+ lut = lut[::step]
+ elif len(lut) < size:
+ # Over sample
+ indexes = numpy.arange(size) / float(size) * (len(lut) - 1)
+ indexes = indexes.astype("int")
+ lut = lut[indexes]
+ if lut is None or len(lut) == 0:
+ return qt.QIcon()
+
+ pixmap = qt.QPixmap(size, size)
+ painter = qt.QPainter(pixmap)
+ for i in range(size):
+ rgb = lut[i]
+ r, g, b = rgb[0], rgb[1], rgb[2]
+ painter.setPen(qt.QColor(r, g, b))
+ painter.drawPoint(qt.QPoint(i, 0))
+
+ painter.drawPixmap(0, 1, size, size - 1, pixmap, 0, 0, size, 1)
+ painter.end()
+
+ return qt.QIcon(pixmap)
+
+ def getCurrentName(self):
+ return self.itemData(self.currentIndex(), self.LUT_NAME)
+
+ def getCurrentColors(self):
+ return self.itemData(self.currentIndex(), self.LUT_COLORS)
+
+ def findLutName(self, name):
+ return self.findData(name, role=self.LUT_NAME)
+
+ def findLutColors(self, lut):
+ for index in range(self.count()):
+ if self.itemData(index, role=self.LUT_NAME) is not None:
+ continue
+ colors = self.itemData(index, role=self.LUT_COLORS)
+ if colors is None:
+ continue
+ if numpy.array_equal(colors, lut):
+ return index
+ return -1
+
+ def setCurrentLut(self, colormap):
+ name = colormap.getName()
+ if name is not None:
+ self._setCurrentName(name)
+ else:
+ lut = colormap.getColormapLUT()
+ self._setCurrentLut(lut)
+
+ def _setCurrentLut(self, lut):
+ index = self.findLutColors(lut)
+ if index == -1:
+ index = self.count()
+ self.addItem("Custom")
+ self.setItemIcon(index, self.getIconPreview(colors=lut))
+ self.setItemData(index, None, role=self.LUT_NAME)
+ self.setItemData(index, lut, role=self.LUT_COLORS)
+ self.setCurrentIndex(index)
+
+ def _setCurrentName(self, name):
+ index = self.findLutName(name)
+ if index < 0:
+ index = self.count()
+ self.addItem(str.title(name))
+ self.setItemIcon(index, self.getIconPreview(name=name))
+ self.setItemData(index, name, role=self.LUT_NAME)
+ self.setCurrentIndex(index)
diff --git a/silx/gui/widgets/FrameBrowser.py b/silx/gui/widgets/FrameBrowser.py
index b4f88fc..671991f 100644
--- a/silx/gui/widgets/FrameBrowser.py
+++ b/silx/gui/widgets/FrameBrowser.py
@@ -95,7 +95,7 @@ class FrameBrowser(qt.QWidget):
else:
first, last = 0, n
- self._lineEdit.setFixedWidth(self._lineEdit.fontMetrics().width('%05d' % last))
+ self._lineEdit.setFixedWidth(self._lineEdit.fontMetrics().boundingRect('%05d' % last).width())
validator = qt.QIntValidator(first, last, self._lineEdit)
self._lineEdit.setValidator(validator)
self._lineEdit.setText("%d" % first)
diff --git a/silx/gui/widgets/LegendIconWidget.py b/silx/gui/widgets/LegendIconWidget.py
new file mode 100755
index 0000000..1a403cb
--- /dev/null
+++ b/silx/gui/widgets/LegendIconWidget.py
@@ -0,0 +1,513 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2004-2018 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""Widget displaying a symbol (marker symbol, line style and color) to identify
+an item displayed by a plot.
+"""
+
+__authors__ = ["V.A. Sole", "T. Rueter", "T. Vincent"]
+__license__ = "MIT"
+__data__ = "11/11/2019"
+
+
+import logging
+
+import numpy
+
+from .. import qt, colors
+
+
+_logger = logging.getLogger(__name__)
+
+
+# Build all symbols
+# Courtesy of the pyqtgraph project
+
+_Symbols = None
+""""Cache supported symbols as Qt paths"""
+
+
+_NoSymbols = (None, 'None', 'none', '', ' ')
+"""List of values resulting in no symbol being displayed for a curve"""
+
+
+_LineStyles = {
+ None: qt.Qt.NoPen,
+ 'None': qt.Qt.NoPen,
+ 'none': qt.Qt.NoPen,
+ '': qt.Qt.NoPen,
+ ' ': qt.Qt.NoPen,
+ '-': qt.Qt.SolidLine,
+ '--': qt.Qt.DashLine,
+ ':': qt.Qt.DotLine,
+ '-.': qt.Qt.DashDotLine
+}
+"""Conversion from matplotlib-like linestyle to Qt"""
+
+_NoLineStyle = (None, 'None', 'none', '', ' ')
+"""List of style values resulting in no line being displayed for a curve"""
+
+
+_colormapImage = {}
+"""Store cached pixmap"""
+# FIXME: Could be better to use a LRU dictionary
+
+_COLORMAP_PIXMAP_SIZE = 32
+"""Size of the cached pixmaps for the colormaps"""
+
+
+def _initSymbols():
+ """Init the cached symbol structure if not yet done."""
+ global _Symbols
+ if _Symbols is not None:
+ return
+
+ symbols = dict([(name, qt.QPainterPath())
+ for name in ['o', 's', 't', 'd', '+', 'x', '.', ',']])
+ symbols['o'].addEllipse(qt.QRectF(.1, .1, .8, .8))
+ symbols['.'].addEllipse(qt.QRectF(.3, .3, .4, .4))
+ symbols[','].addEllipse(qt.QRectF(.4, .4, .2, .2))
+ symbols['s'].addRect(qt.QRectF(.1, .1, .8, .8))
+
+ coords = {
+ 't': [(0.5, 0.), (.1, .8), (.9, .8)],
+ 'd': [(0.1, 0.5), (0.5, 0.), (0.9, 0.5), (0.5, 1.)],
+ '+': [(0.0, 0.40), (0.40, 0.40), (0.40, 0.), (0.60, 0.),
+ (0.60, 0.40), (1., 0.40), (1., 0.60), (0.60, 0.60),
+ (0.60, 1.), (0.40, 1.), (0.40, 0.60), (0., 0.60)],
+ 'x': [(0.0, 0.40), (0.40, 0.40), (0.40, 0.), (0.60, 0.),
+ (0.60, 0.40), (1., 0.40), (1., 0.60), (0.60, 0.60),
+ (0.60, 1.), (0.40, 1.), (0.40, 0.60), (0., 0.60)]
+ }
+ for s, c in coords.items():
+ symbols[s].moveTo(*c[0])
+ for x, y in c[1:]:
+ symbols[s].lineTo(x, y)
+ symbols[s].closeSubpath()
+ tr = qt.QTransform()
+ tr.rotate(45)
+ symbols['x'].translate(qt.QPointF(-0.5, -0.5))
+ symbols['x'] = tr.map(symbols['x'])
+ symbols['x'].translate(qt.QPointF(0.5, 0.5))
+
+ _Symbols = symbols
+
+
+class LegendIconWidget(qt.QWidget):
+ """Object displaying linestyle and symbol of plots.
+
+ :param QWidget parent: See :class:`QWidget`
+ """
+
+ def __init__(self, parent=None):
+ super(LegendIconWidget, self).__init__(parent)
+ _initSymbols()
+
+ # Visibilities
+ self.showLine = True
+ self.showSymbol = True
+ self.showColormap = True
+
+ # Line attributes
+ self.lineStyle = qt.Qt.NoPen
+ self.lineWidth = 1.
+ self.lineColor = qt.Qt.green
+
+ self.symbol = ''
+ # Symbol attributes
+ self.symbolStyle = qt.Qt.SolidPattern
+ self.symbolColor = qt.Qt.green
+ self.symbolOutlineBrush = qt.QBrush(qt.Qt.white)
+ self.symbolColormap = None
+ """Name or array of colors"""
+
+ self.colormap = None
+ """Name or array of colors"""
+
+ # Control widget size: sizeHint "is the only acceptable
+ # alternative, so the widget can never grow or shrink"
+ # (c.f. Qt Doc, enum QSizePolicy::Policy)
+ self.setSizePolicy(qt.QSizePolicy.Fixed,
+ qt.QSizePolicy.Fixed)
+
+ def sizeHint(self):
+ return qt.QSize(50, 15)
+
+ def setSymbol(self, symbol):
+ """Set the symbol"""
+ symbol = str(symbol)
+ if symbol not in _NoSymbols:
+ if symbol not in _Symbols:
+ raise ValueError("Unknown symbol: <%s>" % symbol)
+ self.symbol = symbol
+ self.update()
+
+ def setSymbolColor(self, color):
+ """
+ :param color: determines the symbol color
+ :type style: qt.QColor
+ """
+ self.symbolColor = qt.QColor(color)
+ self.update()
+
+ # Modify Line
+
+ def setLineColor(self, color):
+ self.lineColor = qt.QColor(color)
+ self.update()
+
+ def setLineWidth(self, width):
+ self.lineWidth = float(width)
+ self.update()
+
+ def setLineStyle(self, style):
+ """Set the linestyle.
+
+ Possible line styles:
+
+ - '', ' ', 'None': No line
+ - '-': solid
+ - '--': dashed
+ - ':': dotted
+ - '-.': dash and dot
+
+ :param str style: The linestyle to use
+ """
+ if style not in _LineStyles:
+ raise ValueError('Unknown style: %s', style)
+ self.lineStyle = _LineStyles[style]
+ self.update()
+
+ def _toLut(self, colormap):
+ """Returns an internal LUT object used by this widget to manage
+ a colormap LUT.
+
+ If the argument is a `Colormap` object, only the current state will be
+ displayed. The object itself will not be stored, and further changes
+ of this `Colormap` will not update this widget.
+
+ :param Union[str,numpy.ndarray,Colormap] colormap: The colormap to
+ display
+ :rtype: Union[None,str,numpy.ndarray]
+ """
+ if isinstance(colormap, colors.Colormap):
+ # Helper to allow to support Colormap objects
+ c = colormap.getName()
+ if c is None:
+ c = colormap.getNColors()
+ colormap = c
+
+ return colormap
+
+ def setColormap(self, colormap):
+ """Set the colormap to display
+
+ If the argument is a `Colormap` object, only the current state will be
+ displayed. The object itself will not be stored, and further changes
+ of this `Colormap` will not update this widget.
+
+ :param Union[str,numpy.ndarray,Colormap] colormap: The colormap to
+ display
+ """
+ colormap = self._toLut(colormap)
+
+ if colormap is None:
+ if self.colormap is None:
+ return
+ self.colormap = None
+ self.update()
+ return
+
+ if numpy.array_equal(self.colormap, colormap):
+ # This also works with strings
+ return
+
+ self.colormap = colormap
+ self.update()
+
+ def getColormap(self):
+ """Returns the used colormap.
+
+ If the argument was set with a `Colormap` object, this function will
+ returns the LUT, represented by a string name or by an array or colors.
+
+ :returns: Union[None,str,numpy.ndarray,Colormap]
+ """
+ return self.colormap
+
+ def setSymbolColormap(self, colormap):
+ """Set the colormap to display a symbol
+
+ If the argument is a `Colormap` object, only the current state will be
+ displayed. The object itself will not be stored, and further changes
+ of this `Colormap` will not update this widget.
+
+ :param Union[str,numpy.ndarray,Colormap] colormap: The colormap to
+ display
+ """
+ colormap = self._toLut(colormap)
+
+ if colormap is None:
+ if self.colormap is None:
+ return
+ self.symbolColormap = None
+ self.update()
+ return
+
+ if numpy.array_equal(self.symbolColormap, colormap):
+ # This also works with strings
+ return
+
+ self.symbolColormap = colormap
+ self.update()
+
+ def getSymbolColormap(self):
+ """Returns the used symbol colormap.
+
+ If the argument was set with a `Colormap` object, this function will
+ returns the LUT, represented by a string name or by an array or colors.
+
+ :returns: Union[None,str,numpy.ndarray,Colormap]
+ """
+ return self.colormap
+
+ # Paint
+
+ def paintEvent(self, event):
+ """
+ :param event: event
+ :type event: QPaintEvent
+ """
+ painter = qt.QPainter(self)
+ self.paint(painter, event.rect(), self.palette())
+
+ def paint(self, painter, rect, palette):
+ painter.save()
+ painter.setRenderHint(qt.QPainter.Antialiasing)
+ # Scale painter to the icon height
+ # current -> width = 2.5, height = 1.0
+ scale = float(self.height())
+ ratio = float(self.width()) / scale
+ symbolOffset = qt.QPointF(.5 * (ratio - 1.), 0.)
+ # Determine and scale offset
+ offset = qt.QPointF(float(rect.left()) / scale, float(rect.top()) / scale)
+
+ # Override color when disabled
+ if self.isEnabled():
+ overrideColor = None
+ else:
+ overrideColor = palette.color(qt.QPalette.Disabled,
+ qt.QPalette.WindowText)
+
+ # Draw BG rectangle (for debugging)
+ # bottomRight = qt.QPointF(
+ # float(rect.right())/scale,
+ # float(rect.bottom())/scale)
+ # painter.fillRect(qt.QRectF(offset, bottomRight),
+ # qt.QBrush(qt.Qt.green))
+
+ if self.showColormap:
+ if self.colormap is not None:
+ if self.isEnabled():
+ image = self.getColormapImage(self.colormap)
+ else:
+ image = self.getGrayedColormapImage(self.colormap)
+ pixmapRect = qt.QRect(0, 0, _COLORMAP_PIXMAP_SIZE, 1)
+ widthMargin = 0
+ halfHeight = 4
+ dest = qt.QRect(
+ rect.left() + widthMargin,
+ rect.center().y() - halfHeight + 1,
+ rect.width() - widthMargin * 2,
+ halfHeight * 2,
+ )
+ painter.drawImage(dest, image, pixmapRect)
+
+ painter.scale(scale, scale)
+
+ llist = []
+ if self.showLine:
+ linePath = qt.QPainterPath()
+ linePath.moveTo(0., 0.5)
+ linePath.lineTo(ratio, 0.5)
+ # linePath.lineTo(2.5, 0.5)
+ lineBrush = qt.QBrush(
+ self.lineColor if overrideColor is None else overrideColor)
+ linePen = qt.QPen(
+ lineBrush,
+ (self.lineWidth / self.height()),
+ self.lineStyle,
+ qt.Qt.FlatCap
+ )
+ llist.append((linePath, linePen, lineBrush))
+
+ isValidSymbol = (len(self.symbol) and
+ self.symbol not in _NoSymbols)
+ if self.showSymbol and isValidSymbol:
+ if self.symbolColormap is None:
+ # PITFALL ahead: Let this be a warning to others
+ # symbolPath = Symbols[self.symbol]
+ # Copy before translate! Dict is a mutable type
+ symbolPath = qt.QPainterPath(_Symbols[self.symbol])
+ symbolPath.translate(symbolOffset)
+ symbolBrush = qt.QBrush(
+ self.symbolColor if overrideColor is None else overrideColor,
+ self.symbolStyle)
+ symbolPen = qt.QPen(
+ self.symbolOutlineBrush, # Brush
+ 1. / self.height(), # Width
+ qt.Qt.SolidLine # Style
+ )
+ llist.append((symbolPath,
+ symbolPen,
+ symbolBrush))
+ else:
+ nbSymbols = int(ratio + 2)
+ for i in range(nbSymbols):
+ if self.isEnabled():
+ image = self.getColormapImage(self.symbolColormap)
+ else:
+ image = self.getGrayedColormapImage(self.symbolColormap)
+ pos = int((_COLORMAP_PIXMAP_SIZE / nbSymbols) * i)
+ pos = numpy.clip(pos, 0, _COLORMAP_PIXMAP_SIZE-1)
+ color = image.pixelColor(pos, 0)
+ delta = qt.QPointF(ratio * ((i - (nbSymbols-1)/2) / nbSymbols), 0)
+
+ symbolPath = qt.QPainterPath(_Symbols[self.symbol])
+ symbolPath.translate(symbolOffset + delta)
+ symbolBrush = qt.QBrush(color, self.symbolStyle)
+ symbolPen = qt.QPen(
+ self.symbolOutlineBrush, # Brush
+ 1. / self.height(), # Width
+ qt.Qt.SolidLine # Style
+ )
+ llist.append((symbolPath,
+ symbolPen,
+ symbolBrush))
+
+ # Draw
+ for path, pen, brush in llist:
+ path.translate(offset)
+ painter.setPen(pen)
+ painter.setBrush(brush)
+ painter.drawPath(path)
+
+ painter.restore()
+
+ # Helpers
+
+ @staticmethod
+ def isEmptySymbol(symbol):
+ """Returns True if this symbol description will result in an empty
+ symbol."""
+ return symbol in _NoSymbols
+
+ @staticmethod
+ def isEmptyLineStyle(lineStyle):
+ """Returns True if this line style description will result in an empty
+ line."""
+ return lineStyle in _NoLineStyle
+
+ @staticmethod
+ def _getColormapKey(colormap):
+ """
+ Returns the key used to store the image in the data storage
+ """
+ if isinstance(colormap, numpy.ndarray):
+ key = tuple(colormap)
+ else:
+ key = colormap
+ return key
+
+ @staticmethod
+ def getGrayedColormapImage(colormap):
+ """Return a grayed version image preview from a LUT name.
+
+ This images are cached into a global structure.
+
+ :param Union[str,numpy.ndarray] colormap: Description of the LUT
+ :rtype: qt.QImage
+ """
+ key = LegendIconWidget._getColormapKey(colormap)
+ grayKey = (key, "gray")
+ image = _colormapImage.get(grayKey, None)
+ if image is None:
+ image = LegendIconWidget.getColormapImage(colormap)
+ image = image.convertToFormat(qt.QImage.Format_Grayscale8)
+ _colormapImage[grayKey] = image
+ return image
+
+ @staticmethod
+ def getColormapImage(colormap):
+ """Return an image preview from a LUT name.
+
+ This images are cached into a global structure.
+
+ :param Union[str,numpy.ndarray] colormap: Description of the LUT
+ :rtype: qt.QImage
+ """
+ key = LegendIconWidget._getColormapKey(colormap)
+ image = _colormapImage.get(key, None)
+ if image is None:
+ image = LegendIconWidget.createColormapImage(colormap)
+ _colormapImage[key] = image
+ return image
+
+ @staticmethod
+ def createColormapImage(colormap):
+ """Create and return an icon preview from a LUT name.
+
+ This icons are cached into a global structure.
+
+ :param Union[str,numpy.ndarray] colormap: Description of the LUT
+ :rtype: qt.QImage
+ """
+ size = _COLORMAP_PIXMAP_SIZE
+ if isinstance(colormap, numpy.ndarray):
+ lut = colormap
+ if len(lut) > size:
+ # Down sample
+ step = int(len(lut) / size)
+ lut = lut[::step]
+ elif len(lut) < size:
+ # Over sample
+ indexes = numpy.arange(size) / float(size) * (len(lut) - 1)
+ indexes = indexes.astype("int")
+ lut = lut[indexes]
+ else:
+ colormap = colors.Colormap(colormap)
+ lut = colormap.getNColors(size)
+
+ if lut is None or len(lut) == 0:
+ return qt.QIcon()
+
+ pixmap = qt.QPixmap(size, 1)
+ painter = qt.QPainter(pixmap)
+ for i in range(size):
+ rgb = lut[i]
+ r, g, b = rgb[0], rgb[1], rgb[2]
+ painter.setPen(qt.QColor(r, g, b))
+ painter.drawPoint(qt.QPoint(i, 0))
+ painter.end()
+ return pixmap.toImage()
diff --git a/silx/gui/widgets/RangeSlider.py b/silx/gui/widgets/RangeSlider.py
index 0cf195c..c352147 100644
--- a/silx/gui/widgets/RangeSlider.py
+++ b/silx/gui/widgets/RangeSlider.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2015-2018 European Synchrotron Radiation Facility
+# Copyright (c) 2015-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -466,7 +466,7 @@ class RangeSlider(qt.QWidget):
:param Union[numpy.ndarray,None] profile:
1D array of values to display
- :param Union[Colormap,str] colormap:
+ :param Union[~silx.gui.colors.Colormap,str] colormap:
The colormap name or object to convert profile values to colors
"""
if profile is None:
diff --git a/silx/image/bilinear.c b/silx/image/bilinear.c
deleted file mode 100644
index 85b293e..0000000
--- a/silx/image/bilinear.c
+++ /dev/null
@@ -1,24879 +0,0 @@
-/* Generated by Cython 0.29.11 */
-
-/* BEGIN: Cython Metadata
-{
- "distutils": {
- "depends": [],
- "language": "c",
- "name": "silx.image.bilinear",
- "sources": [
- "silx/image/bilinear.pyx"
- ]
- },
- "module_name": "silx.image.bilinear"
-}
-END: Cython Metadata */
-
-#define PY_SSIZE_T_CLEAN
-#include "Python.h"
-#ifndef Py_PYTHON_H
- #error Python headers needed to compile C extensions, please install development version of Python.
-#elif PY_VERSION_HEX < 0x02060000 || (0x03000000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x03030000)
- #error Cython requires Python 2.6+ or Python 3.3+.
-#else
-#define CYTHON_ABI "0_29_11"
-#define CYTHON_HEX_VERSION 0x001D0BF0
-#define CYTHON_FUTURE_DIVISION 1
-#include <stddef.h>
-#ifndef offsetof
- #define offsetof(type, member) ( (size_t) & ((type*)0) -> member )
-#endif
-#if !defined(WIN32) && !defined(MS_WINDOWS)
- #ifndef __stdcall
- #define __stdcall
- #endif
- #ifndef __cdecl
- #define __cdecl
- #endif
- #ifndef __fastcall
- #define __fastcall
- #endif
-#endif
-#ifndef DL_IMPORT
- #define DL_IMPORT(t) t
-#endif
-#ifndef DL_EXPORT
- #define DL_EXPORT(t) t
-#endif
-#define __PYX_COMMA ,
-#ifndef HAVE_LONG_LONG
- #if PY_VERSION_HEX >= 0x02070000
- #define HAVE_LONG_LONG
- #endif
-#endif
-#ifndef PY_LONG_LONG
- #define PY_LONG_LONG LONG_LONG
-#endif
-#ifndef Py_HUGE_VAL
- #define Py_HUGE_VAL HUGE_VAL
-#endif
-#ifdef PYPY_VERSION
- #define CYTHON_COMPILING_IN_PYPY 1
- #define CYTHON_COMPILING_IN_PYSTON 0
- #define CYTHON_COMPILING_IN_CPYTHON 0
- #undef CYTHON_USE_TYPE_SLOTS
- #define CYTHON_USE_TYPE_SLOTS 0
- #undef CYTHON_USE_PYTYPE_LOOKUP
- #define CYTHON_USE_PYTYPE_LOOKUP 0
- #if PY_VERSION_HEX < 0x03050000
- #undef CYTHON_USE_ASYNC_SLOTS
- #define CYTHON_USE_ASYNC_SLOTS 0
- #elif !defined(CYTHON_USE_ASYNC_SLOTS)
- #define CYTHON_USE_ASYNC_SLOTS 1
- #endif
- #undef CYTHON_USE_PYLIST_INTERNALS
- #define CYTHON_USE_PYLIST_INTERNALS 0
- #undef CYTHON_USE_UNICODE_INTERNALS
- #define CYTHON_USE_UNICODE_INTERNALS 0
- #undef CYTHON_USE_UNICODE_WRITER
- #define CYTHON_USE_UNICODE_WRITER 0
- #undef CYTHON_USE_PYLONG_INTERNALS
- #define CYTHON_USE_PYLONG_INTERNALS 0
- #undef CYTHON_AVOID_BORROWED_REFS
- #define CYTHON_AVOID_BORROWED_REFS 1
- #undef CYTHON_ASSUME_SAFE_MACROS
- #define CYTHON_ASSUME_SAFE_MACROS 0
- #undef CYTHON_UNPACK_METHODS
- #define CYTHON_UNPACK_METHODS 0
- #undef CYTHON_FAST_THREAD_STATE
- #define CYTHON_FAST_THREAD_STATE 0
- #undef CYTHON_FAST_PYCALL
- #define CYTHON_FAST_PYCALL 0
- #undef CYTHON_PEP489_MULTI_PHASE_INIT
- #define CYTHON_PEP489_MULTI_PHASE_INIT 0
- #undef CYTHON_USE_TP_FINALIZE
- #define CYTHON_USE_TP_FINALIZE 0
- #undef CYTHON_USE_DICT_VERSIONS
- #define CYTHON_USE_DICT_VERSIONS 0
- #undef CYTHON_USE_EXC_INFO_STACK
- #define CYTHON_USE_EXC_INFO_STACK 0
-#elif defined(PYSTON_VERSION)
- #define CYTHON_COMPILING_IN_PYPY 0
- #define CYTHON_COMPILING_IN_PYSTON 1
- #define CYTHON_COMPILING_IN_CPYTHON 0
- #ifndef CYTHON_USE_TYPE_SLOTS
- #define CYTHON_USE_TYPE_SLOTS 1
- #endif
- #undef CYTHON_USE_PYTYPE_LOOKUP
- #define CYTHON_USE_PYTYPE_LOOKUP 0
- #undef CYTHON_USE_ASYNC_SLOTS
- #define CYTHON_USE_ASYNC_SLOTS 0
- #undef CYTHON_USE_PYLIST_INTERNALS
- #define CYTHON_USE_PYLIST_INTERNALS 0
- #ifndef CYTHON_USE_UNICODE_INTERNALS
- #define CYTHON_USE_UNICODE_INTERNALS 1
- #endif
- #undef CYTHON_USE_UNICODE_WRITER
- #define CYTHON_USE_UNICODE_WRITER 0
- #undef CYTHON_USE_PYLONG_INTERNALS
- #define CYTHON_USE_PYLONG_INTERNALS 0
- #ifndef CYTHON_AVOID_BORROWED_REFS
- #define CYTHON_AVOID_BORROWED_REFS 0
- #endif
- #ifndef CYTHON_ASSUME_SAFE_MACROS
- #define CYTHON_ASSUME_SAFE_MACROS 1
- #endif
- #ifndef CYTHON_UNPACK_METHODS
- #define CYTHON_UNPACK_METHODS 1
- #endif
- #undef CYTHON_FAST_THREAD_STATE
- #define CYTHON_FAST_THREAD_STATE 0
- #undef CYTHON_FAST_PYCALL
- #define CYTHON_FAST_PYCALL 0
- #undef CYTHON_PEP489_MULTI_PHASE_INIT
- #define CYTHON_PEP489_MULTI_PHASE_INIT 0
- #undef CYTHON_USE_TP_FINALIZE
- #define CYTHON_USE_TP_FINALIZE 0
- #undef CYTHON_USE_DICT_VERSIONS
- #define CYTHON_USE_DICT_VERSIONS 0
- #undef CYTHON_USE_EXC_INFO_STACK
- #define CYTHON_USE_EXC_INFO_STACK 0
-#else
- #define CYTHON_COMPILING_IN_PYPY 0
- #define CYTHON_COMPILING_IN_PYSTON 0
- #define CYTHON_COMPILING_IN_CPYTHON 1
- #ifndef CYTHON_USE_TYPE_SLOTS
- #define CYTHON_USE_TYPE_SLOTS 1
- #endif
- #if PY_VERSION_HEX < 0x02070000
- #undef CYTHON_USE_PYTYPE_LOOKUP
- #define CYTHON_USE_PYTYPE_LOOKUP 0
- #elif !defined(CYTHON_USE_PYTYPE_LOOKUP)
- #define CYTHON_USE_PYTYPE_LOOKUP 1
- #endif
- #if PY_MAJOR_VERSION < 3
- #undef CYTHON_USE_ASYNC_SLOTS
- #define CYTHON_USE_ASYNC_SLOTS 0
- #elif !defined(CYTHON_USE_ASYNC_SLOTS)
- #define CYTHON_USE_ASYNC_SLOTS 1
- #endif
- #if PY_VERSION_HEX < 0x02070000
- #undef CYTHON_USE_PYLONG_INTERNALS
- #define CYTHON_USE_PYLONG_INTERNALS 0
- #elif !defined(CYTHON_USE_PYLONG_INTERNALS)
- #define CYTHON_USE_PYLONG_INTERNALS 1
- #endif
- #ifndef CYTHON_USE_PYLIST_INTERNALS
- #define CYTHON_USE_PYLIST_INTERNALS 1
- #endif
- #ifndef CYTHON_USE_UNICODE_INTERNALS
- #define CYTHON_USE_UNICODE_INTERNALS 1
- #endif
- #if PY_VERSION_HEX < 0x030300F0
- #undef CYTHON_USE_UNICODE_WRITER
- #define CYTHON_USE_UNICODE_WRITER 0
- #elif !defined(CYTHON_USE_UNICODE_WRITER)
- #define CYTHON_USE_UNICODE_WRITER 1
- #endif
- #ifndef CYTHON_AVOID_BORROWED_REFS
- #define CYTHON_AVOID_BORROWED_REFS 0
- #endif
- #ifndef CYTHON_ASSUME_SAFE_MACROS
- #define CYTHON_ASSUME_SAFE_MACROS 1
- #endif
- #ifndef CYTHON_UNPACK_METHODS
- #define CYTHON_UNPACK_METHODS 1
- #endif
- #ifndef CYTHON_FAST_THREAD_STATE
- #define CYTHON_FAST_THREAD_STATE 1
- #endif
- #ifndef CYTHON_FAST_PYCALL
- #define CYTHON_FAST_PYCALL 1
- #endif
- #ifndef CYTHON_PEP489_MULTI_PHASE_INIT
- #define CYTHON_PEP489_MULTI_PHASE_INIT (PY_VERSION_HEX >= 0x03050000)
- #endif
- #ifndef CYTHON_USE_TP_FINALIZE
- #define CYTHON_USE_TP_FINALIZE (PY_VERSION_HEX >= 0x030400a1)
- #endif
- #ifndef CYTHON_USE_DICT_VERSIONS
- #define CYTHON_USE_DICT_VERSIONS (PY_VERSION_HEX >= 0x030600B1)
- #endif
- #ifndef CYTHON_USE_EXC_INFO_STACK
- #define CYTHON_USE_EXC_INFO_STACK (PY_VERSION_HEX >= 0x030700A3)
- #endif
-#endif
-#if !defined(CYTHON_FAST_PYCCALL)
-#define CYTHON_FAST_PYCCALL (CYTHON_FAST_PYCALL && PY_VERSION_HEX >= 0x030600B1)
-#endif
-#if CYTHON_USE_PYLONG_INTERNALS
- #include "longintrepr.h"
- #undef SHIFT
- #undef BASE
- #undef MASK
- #ifdef SIZEOF_VOID_P
- enum { __pyx_check_sizeof_voidp = 1 / (int)(SIZEOF_VOID_P == sizeof(void*)) };
- #endif
-#endif
-#ifndef __has_attribute
- #define __has_attribute(x) 0
-#endif
-#ifndef __has_cpp_attribute
- #define __has_cpp_attribute(x) 0
-#endif
-#ifndef CYTHON_RESTRICT
- #if defined(__GNUC__)
- #define CYTHON_RESTRICT __restrict__
- #elif defined(_MSC_VER) && _MSC_VER >= 1400
- #define CYTHON_RESTRICT __restrict
- #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
- #define CYTHON_RESTRICT restrict
- #else
- #define CYTHON_RESTRICT
- #endif
-#endif
-#ifndef CYTHON_UNUSED
-# if defined(__GNUC__)
-# if !(defined(__cplusplus)) || (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))
-# define CYTHON_UNUSED __attribute__ ((__unused__))
-# else
-# define CYTHON_UNUSED
-# endif
-# elif defined(__ICC) || (defined(__INTEL_COMPILER) && !defined(_MSC_VER))
-# define CYTHON_UNUSED __attribute__ ((__unused__))
-# else
-# define CYTHON_UNUSED
-# endif
-#endif
-#ifndef CYTHON_MAYBE_UNUSED_VAR
-# if defined(__cplusplus)
- template<class T> void CYTHON_MAYBE_UNUSED_VAR( const T& ) { }
-# else
-# define CYTHON_MAYBE_UNUSED_VAR(x) (void)(x)
-# endif
-#endif
-#ifndef CYTHON_NCP_UNUSED
-# if CYTHON_COMPILING_IN_CPYTHON
-# define CYTHON_NCP_UNUSED
-# else
-# define CYTHON_NCP_UNUSED CYTHON_UNUSED
-# endif
-#endif
-#define __Pyx_void_to_None(void_result) ((void)(void_result), Py_INCREF(Py_None), Py_None)
-#ifdef _MSC_VER
- #ifndef _MSC_STDINT_H_
- #if _MSC_VER < 1300
- typedef unsigned char uint8_t;
- typedef unsigned int uint32_t;
- #else
- typedef unsigned __int8 uint8_t;
- typedef unsigned __int32 uint32_t;
- #endif
- #endif
-#else
- #include <stdint.h>
-#endif
-#ifndef CYTHON_FALLTHROUGH
- #if defined(__cplusplus) && __cplusplus >= 201103L
- #if __has_cpp_attribute(fallthrough)
- #define CYTHON_FALLTHROUGH [[fallthrough]]
- #elif __has_cpp_attribute(clang::fallthrough)
- #define CYTHON_FALLTHROUGH [[clang::fallthrough]]
- #elif __has_cpp_attribute(gnu::fallthrough)
- #define CYTHON_FALLTHROUGH [[gnu::fallthrough]]
- #endif
- #endif
- #ifndef CYTHON_FALLTHROUGH
- #if __has_attribute(fallthrough)
- #define CYTHON_FALLTHROUGH __attribute__((fallthrough))
- #else
- #define CYTHON_FALLTHROUGH
- #endif
- #endif
- #if defined(__clang__ ) && defined(__apple_build_version__)
- #if __apple_build_version__ < 7000000
- #undef CYTHON_FALLTHROUGH
- #define CYTHON_FALLTHROUGH
- #endif
- #endif
-#endif
-
-#ifndef CYTHON_INLINE
- #if defined(__clang__)
- #define CYTHON_INLINE __inline__ __attribute__ ((__unused__))
- #elif defined(__GNUC__)
- #define CYTHON_INLINE __inline__
- #elif defined(_MSC_VER)
- #define CYTHON_INLINE __inline
- #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
- #define CYTHON_INLINE inline
- #else
- #define CYTHON_INLINE
- #endif
-#endif
-
-#if CYTHON_COMPILING_IN_PYPY && PY_VERSION_HEX < 0x02070600 && !defined(Py_OptimizeFlag)
- #define Py_OptimizeFlag 0
-#endif
-#define __PYX_BUILD_PY_SSIZE_T "n"
-#define CYTHON_FORMAT_SSIZE_T "z"
-#if PY_MAJOR_VERSION < 3
- #define __Pyx_BUILTIN_MODULE_NAME "__builtin__"
- #define __Pyx_PyCode_New(a, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)\
- PyCode_New(a+k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)
- #define __Pyx_DefaultClassType PyClass_Type
-#else
- #define __Pyx_BUILTIN_MODULE_NAME "builtins"
-#if PY_VERSION_HEX < 0x030800A4
- #define __Pyx_PyCode_New(a, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)\
- PyCode_New(a, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)
-#elif PY_VERSION_HEX >= 0x030800B2
- #define __Pyx_PyCode_New(a, p, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)\
- PyCode_NewWithPosOnlyArgs(a, p, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)
-#else
- #define __Pyx_PyCode_New(a, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)\
- PyCode_New(a, 0, k, l, s, f, code, c, n, v, fv, cell, fn, name, fline, lnos)
-#endif
- #define __Pyx_DefaultClassType PyType_Type
-#endif
-#ifndef Py_TPFLAGS_CHECKTYPES
- #define Py_TPFLAGS_CHECKTYPES 0
-#endif
-#ifndef Py_TPFLAGS_HAVE_INDEX
- #define Py_TPFLAGS_HAVE_INDEX 0
-#endif
-#ifndef Py_TPFLAGS_HAVE_NEWBUFFER
- #define Py_TPFLAGS_HAVE_NEWBUFFER 0
-#endif
-#ifndef Py_TPFLAGS_HAVE_FINALIZE
- #define Py_TPFLAGS_HAVE_FINALIZE 0
-#endif
-#ifndef METH_STACKLESS
- #define METH_STACKLESS 0
-#endif
-#if PY_VERSION_HEX <= 0x030700A3 || !defined(METH_FASTCALL)
- #ifndef METH_FASTCALL
- #define METH_FASTCALL 0x80
- #endif
- typedef PyObject *(*__Pyx_PyCFunctionFast) (PyObject *self, PyObject *const *args, Py_ssize_t nargs);
- typedef PyObject *(*__Pyx_PyCFunctionFastWithKeywords) (PyObject *self, PyObject *const *args,
- Py_ssize_t nargs, PyObject *kwnames);
-#else
- #define __Pyx_PyCFunctionFast _PyCFunctionFast
- #define __Pyx_PyCFunctionFastWithKeywords _PyCFunctionFastWithKeywords
-#endif
-#if CYTHON_FAST_PYCCALL
-#define __Pyx_PyFastCFunction_Check(func)\
- ((PyCFunction_Check(func) && (METH_FASTCALL == (PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST | METH_KEYWORDS | METH_STACKLESS)))))
-#else
-#define __Pyx_PyFastCFunction_Check(func) 0
-#endif
-#if CYTHON_COMPILING_IN_PYPY && !defined(PyObject_Malloc)
- #define PyObject_Malloc(s) PyMem_Malloc(s)
- #define PyObject_Free(p) PyMem_Free(p)
- #define PyObject_Realloc(p) PyMem_Realloc(p)
-#endif
-#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX < 0x030400A1
- #define PyMem_RawMalloc(n) PyMem_Malloc(n)
- #define PyMem_RawRealloc(p, n) PyMem_Realloc(p, n)
- #define PyMem_RawFree(p) PyMem_Free(p)
-#endif
-#if CYTHON_COMPILING_IN_PYSTON
- #define __Pyx_PyCode_HasFreeVars(co) PyCode_HasFreeVars(co)
- #define __Pyx_PyFrame_SetLineNumber(frame, lineno) PyFrame_SetLineNumber(frame, lineno)
-#else
- #define __Pyx_PyCode_HasFreeVars(co) (PyCode_GetNumFree(co) > 0)
- #define __Pyx_PyFrame_SetLineNumber(frame, lineno) (frame)->f_lineno = (lineno)
-#endif
-#if !CYTHON_FAST_THREAD_STATE || PY_VERSION_HEX < 0x02070000
- #define __Pyx_PyThreadState_Current PyThreadState_GET()
-#elif PY_VERSION_HEX >= 0x03060000
- #define __Pyx_PyThreadState_Current _PyThreadState_UncheckedGet()
-#elif PY_VERSION_HEX >= 0x03000000
- #define __Pyx_PyThreadState_Current PyThreadState_GET()
-#else
- #define __Pyx_PyThreadState_Current _PyThreadState_Current
-#endif
-#if PY_VERSION_HEX < 0x030700A2 && !defined(PyThread_tss_create) && !defined(Py_tss_NEEDS_INIT)
-#include "pythread.h"
-#define Py_tss_NEEDS_INIT 0
-typedef int Py_tss_t;
-static CYTHON_INLINE int PyThread_tss_create(Py_tss_t *key) {
- *key = PyThread_create_key();
- return 0;
-}
-static CYTHON_INLINE Py_tss_t * PyThread_tss_alloc(void) {
- Py_tss_t *key = (Py_tss_t *)PyObject_Malloc(sizeof(Py_tss_t));
- *key = Py_tss_NEEDS_INIT;
- return key;
-}
-static CYTHON_INLINE void PyThread_tss_free(Py_tss_t *key) {
- PyObject_Free(key);
-}
-static CYTHON_INLINE int PyThread_tss_is_created(Py_tss_t *key) {
- return *key != Py_tss_NEEDS_INIT;
-}
-static CYTHON_INLINE void PyThread_tss_delete(Py_tss_t *key) {
- PyThread_delete_key(*key);
- *key = Py_tss_NEEDS_INIT;
-}
-static CYTHON_INLINE int PyThread_tss_set(Py_tss_t *key, void *value) {
- return PyThread_set_key_value(*key, value);
-}
-static CYTHON_INLINE void * PyThread_tss_get(Py_tss_t *key) {
- return PyThread_get_key_value(*key);
-}
-#endif
-#if CYTHON_COMPILING_IN_CPYTHON || defined(_PyDict_NewPresized)
-#define __Pyx_PyDict_NewPresized(n) ((n <= 8) ? PyDict_New() : _PyDict_NewPresized(n))
-#else
-#define __Pyx_PyDict_NewPresized(n) PyDict_New()
-#endif
-#if PY_MAJOR_VERSION >= 3 || CYTHON_FUTURE_DIVISION
- #define __Pyx_PyNumber_Divide(x,y) PyNumber_TrueDivide(x,y)
- #define __Pyx_PyNumber_InPlaceDivide(x,y) PyNumber_InPlaceTrueDivide(x,y)
-#else
- #define __Pyx_PyNumber_Divide(x,y) PyNumber_Divide(x,y)
- #define __Pyx_PyNumber_InPlaceDivide(x,y) PyNumber_InPlaceDivide(x,y)
-#endif
-#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030500A1 && CYTHON_USE_UNICODE_INTERNALS
-#define __Pyx_PyDict_GetItemStr(dict, name) _PyDict_GetItem_KnownHash(dict, name, ((PyASCIIObject *) name)->hash)
-#else
-#define __Pyx_PyDict_GetItemStr(dict, name) PyDict_GetItem(dict, name)
-#endif
-#if PY_VERSION_HEX > 0x03030000 && defined(PyUnicode_KIND)
- #define CYTHON_PEP393_ENABLED 1
- #define __Pyx_PyUnicode_READY(op) (likely(PyUnicode_IS_READY(op)) ?\
- 0 : _PyUnicode_Ready((PyObject *)(op)))
- #define __Pyx_PyUnicode_GET_LENGTH(u) PyUnicode_GET_LENGTH(u)
- #define __Pyx_PyUnicode_READ_CHAR(u, i) PyUnicode_READ_CHAR(u, i)
- #define __Pyx_PyUnicode_MAX_CHAR_VALUE(u) PyUnicode_MAX_CHAR_VALUE(u)
- #define __Pyx_PyUnicode_KIND(u) PyUnicode_KIND(u)
- #define __Pyx_PyUnicode_DATA(u) PyUnicode_DATA(u)
- #define __Pyx_PyUnicode_READ(k, d, i) PyUnicode_READ(k, d, i)
- #define __Pyx_PyUnicode_WRITE(k, d, i, ch) PyUnicode_WRITE(k, d, i, ch)
- #define __Pyx_PyUnicode_IS_TRUE(u) (0 != (likely(PyUnicode_IS_READY(u)) ? PyUnicode_GET_LENGTH(u) : PyUnicode_GET_SIZE(u)))
-#else
- #define CYTHON_PEP393_ENABLED 0
- #define PyUnicode_1BYTE_KIND 1
- #define PyUnicode_2BYTE_KIND 2
- #define PyUnicode_4BYTE_KIND 4
- #define __Pyx_PyUnicode_READY(op) (0)
- #define __Pyx_PyUnicode_GET_LENGTH(u) PyUnicode_GET_SIZE(u)
- #define __Pyx_PyUnicode_READ_CHAR(u, i) ((Py_UCS4)(PyUnicode_AS_UNICODE(u)[i]))
- #define __Pyx_PyUnicode_MAX_CHAR_VALUE(u) ((sizeof(Py_UNICODE) == 2) ? 65535 : 1114111)
- #define __Pyx_PyUnicode_KIND(u) (sizeof(Py_UNICODE))
- #define __Pyx_PyUnicode_DATA(u) ((void*)PyUnicode_AS_UNICODE(u))
- #define __Pyx_PyUnicode_READ(k, d, i) ((void)(k), (Py_UCS4)(((Py_UNICODE*)d)[i]))
- #define __Pyx_PyUnicode_WRITE(k, d, i, ch) (((void)(k)), ((Py_UNICODE*)d)[i] = ch)
- #define __Pyx_PyUnicode_IS_TRUE(u) (0 != PyUnicode_GET_SIZE(u))
-#endif
-#if CYTHON_COMPILING_IN_PYPY
- #define __Pyx_PyUnicode_Concat(a, b) PyNumber_Add(a, b)
- #define __Pyx_PyUnicode_ConcatSafe(a, b) PyNumber_Add(a, b)
-#else
- #define __Pyx_PyUnicode_Concat(a, b) PyUnicode_Concat(a, b)
- #define __Pyx_PyUnicode_ConcatSafe(a, b) ((unlikely((a) == Py_None) || unlikely((b) == Py_None)) ?\
- PyNumber_Add(a, b) : __Pyx_PyUnicode_Concat(a, b))
-#endif
-#if CYTHON_COMPILING_IN_PYPY && !defined(PyUnicode_Contains)
- #define PyUnicode_Contains(u, s) PySequence_Contains(u, s)
-#endif
-#if CYTHON_COMPILING_IN_PYPY && !defined(PyByteArray_Check)
- #define PyByteArray_Check(obj) PyObject_TypeCheck(obj, &PyByteArray_Type)
-#endif
-#if CYTHON_COMPILING_IN_PYPY && !defined(PyObject_Format)
- #define PyObject_Format(obj, fmt) PyObject_CallMethod(obj, "__format__", "O", fmt)
-#endif
-#define __Pyx_PyString_FormatSafe(a, b) ((unlikely((a) == Py_None || (PyString_Check(b) && !PyString_CheckExact(b)))) ? PyNumber_Remainder(a, b) : __Pyx_PyString_Format(a, b))
-#define __Pyx_PyUnicode_FormatSafe(a, b) ((unlikely((a) == Py_None || (PyUnicode_Check(b) && !PyUnicode_CheckExact(b)))) ? PyNumber_Remainder(a, b) : PyUnicode_Format(a, b))
-#if PY_MAJOR_VERSION >= 3
- #define __Pyx_PyString_Format(a, b) PyUnicode_Format(a, b)
-#else
- #define __Pyx_PyString_Format(a, b) PyString_Format(a, b)
-#endif
-#if PY_MAJOR_VERSION < 3 && !defined(PyObject_ASCII)
- #define PyObject_ASCII(o) PyObject_Repr(o)
-#endif
-#if PY_MAJOR_VERSION >= 3
- #define PyBaseString_Type PyUnicode_Type
- #define PyStringObject PyUnicodeObject
- #define PyString_Type PyUnicode_Type
- #define PyString_Check PyUnicode_Check
- #define PyString_CheckExact PyUnicode_CheckExact
- #define PyObject_Unicode PyObject_Str
-#endif
-#if PY_MAJOR_VERSION >= 3
- #define __Pyx_PyBaseString_Check(obj) PyUnicode_Check(obj)
- #define __Pyx_PyBaseString_CheckExact(obj) PyUnicode_CheckExact(obj)
-#else
- #define __Pyx_PyBaseString_Check(obj) (PyString_Check(obj) || PyUnicode_Check(obj))
- #define __Pyx_PyBaseString_CheckExact(obj) (PyString_CheckExact(obj) || PyUnicode_CheckExact(obj))
-#endif
-#ifndef PySet_CheckExact
- #define PySet_CheckExact(obj) (Py_TYPE(obj) == &PySet_Type)
-#endif
-#if CYTHON_ASSUME_SAFE_MACROS
- #define __Pyx_PySequence_SIZE(seq) Py_SIZE(seq)
-#else
- #define __Pyx_PySequence_SIZE(seq) PySequence_Size(seq)
-#endif
-#if PY_MAJOR_VERSION >= 3
- #define PyIntObject PyLongObject
- #define PyInt_Type PyLong_Type
- #define PyInt_Check(op) PyLong_Check(op)
- #define PyInt_CheckExact(op) PyLong_CheckExact(op)
- #define PyInt_FromString PyLong_FromString
- #define PyInt_FromUnicode PyLong_FromUnicode
- #define PyInt_FromLong PyLong_FromLong
- #define PyInt_FromSize_t PyLong_FromSize_t
- #define PyInt_FromSsize_t PyLong_FromSsize_t
- #define PyInt_AsLong PyLong_AsLong
- #define PyInt_AS_LONG PyLong_AS_LONG
- #define PyInt_AsSsize_t PyLong_AsSsize_t
- #define PyInt_AsUnsignedLongMask PyLong_AsUnsignedLongMask
- #define PyInt_AsUnsignedLongLongMask PyLong_AsUnsignedLongLongMask
- #define PyNumber_Int PyNumber_Long
-#endif
-#if PY_MAJOR_VERSION >= 3
- #define PyBoolObject PyLongObject
-#endif
-#if PY_MAJOR_VERSION >= 3 && CYTHON_COMPILING_IN_PYPY
- #ifndef PyUnicode_InternFromString
- #define PyUnicode_InternFromString(s) PyUnicode_FromString(s)
- #endif
-#endif
-#if PY_VERSION_HEX < 0x030200A4
- typedef long Py_hash_t;
- #define __Pyx_PyInt_FromHash_t PyInt_FromLong
- #define __Pyx_PyInt_AsHash_t PyInt_AsLong
-#else
- #define __Pyx_PyInt_FromHash_t PyInt_FromSsize_t
- #define __Pyx_PyInt_AsHash_t PyInt_AsSsize_t
-#endif
-#if PY_MAJOR_VERSION >= 3
- #define __Pyx_PyMethod_New(func, self, klass) ((self) ? PyMethod_New(func, self) : (Py_INCREF(func), func))
-#else
- #define __Pyx_PyMethod_New(func, self, klass) PyMethod_New(func, self, klass)
-#endif
-#if CYTHON_USE_ASYNC_SLOTS
- #if PY_VERSION_HEX >= 0x030500B1
- #define __Pyx_PyAsyncMethodsStruct PyAsyncMethods
- #define __Pyx_PyType_AsAsync(obj) (Py_TYPE(obj)->tp_as_async)
- #else
- #define __Pyx_PyType_AsAsync(obj) ((__Pyx_PyAsyncMethodsStruct*) (Py_TYPE(obj)->tp_reserved))
- #endif
-#else
- #define __Pyx_PyType_AsAsync(obj) NULL
-#endif
-#ifndef __Pyx_PyAsyncMethodsStruct
- typedef struct {
- unaryfunc am_await;
- unaryfunc am_aiter;
- unaryfunc am_anext;
- } __Pyx_PyAsyncMethodsStruct;
-#endif
-
-#if defined(WIN32) || defined(MS_WINDOWS)
- #define _USE_MATH_DEFINES
-#endif
-#include <math.h>
-#ifdef NAN
-#define __PYX_NAN() ((float) NAN)
-#else
-static CYTHON_INLINE float __PYX_NAN() {
- float value;
- memset(&value, 0xFF, sizeof(value));
- return value;
-}
-#endif
-#if defined(__CYGWIN__) && defined(_LDBL_EQ_DBL)
-#define __Pyx_truncl trunc
-#else
-#define __Pyx_truncl truncl
-#endif
-
-
-#define __PYX_ERR(f_index, lineno, Ln_error) \
-{ \
- __pyx_filename = __pyx_f[f_index]; __pyx_lineno = lineno; __pyx_clineno = __LINE__; goto Ln_error; \
-}
-
-#ifndef __PYX_EXTERN_C
- #ifdef __cplusplus
- #define __PYX_EXTERN_C extern "C"
- #else
- #define __PYX_EXTERN_C extern
- #endif
-#endif
-
-#define __PYX_HAVE__silx__image__bilinear
-#define __PYX_HAVE_API__silx__image__bilinear
-/* Early includes */
-#include <math.h>
-#include "pythread.h"
-#include <string.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include "pystate.h"
-#ifdef _OPENMP
-#include <omp.h>
-#endif /* _OPENMP */
-
-#if defined(PYREX_WITHOUT_ASSERTIONS) && !defined(CYTHON_WITHOUT_ASSERTIONS)
-#define CYTHON_WITHOUT_ASSERTIONS
-#endif
-
-typedef struct {PyObject **p; const char *s; const Py_ssize_t n; const char* encoding;
- const char is_unicode; const char is_str; const char intern; } __Pyx_StringTabEntry;
-
-#define __PYX_DEFAULT_STRING_ENCODING_IS_ASCII 0
-#define __PYX_DEFAULT_STRING_ENCODING_IS_UTF8 0
-#define __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT (PY_MAJOR_VERSION >= 3 && __PYX_DEFAULT_STRING_ENCODING_IS_UTF8)
-#define __PYX_DEFAULT_STRING_ENCODING ""
-#define __Pyx_PyObject_FromString __Pyx_PyBytes_FromString
-#define __Pyx_PyObject_FromStringAndSize __Pyx_PyBytes_FromStringAndSize
-#define __Pyx_uchar_cast(c) ((unsigned char)c)
-#define __Pyx_long_cast(x) ((long)x)
-#define __Pyx_fits_Py_ssize_t(v, type, is_signed) (\
- (sizeof(type) < sizeof(Py_ssize_t)) ||\
- (sizeof(type) > sizeof(Py_ssize_t) &&\
- likely(v < (type)PY_SSIZE_T_MAX ||\
- v == (type)PY_SSIZE_T_MAX) &&\
- (!is_signed || likely(v > (type)PY_SSIZE_T_MIN ||\
- v == (type)PY_SSIZE_T_MIN))) ||\
- (sizeof(type) == sizeof(Py_ssize_t) &&\
- (is_signed || likely(v < (type)PY_SSIZE_T_MAX ||\
- v == (type)PY_SSIZE_T_MAX))) )
-static CYTHON_INLINE int __Pyx_is_valid_index(Py_ssize_t i, Py_ssize_t limit) {
- return (size_t) i < (size_t) limit;
-}
-#if defined (__cplusplus) && __cplusplus >= 201103L
- #include <cstdlib>
- #define __Pyx_sst_abs(value) std::abs(value)
-#elif SIZEOF_INT >= SIZEOF_SIZE_T
- #define __Pyx_sst_abs(value) abs(value)
-#elif SIZEOF_LONG >= SIZEOF_SIZE_T
- #define __Pyx_sst_abs(value) labs(value)
-#elif defined (_MSC_VER)
- #define __Pyx_sst_abs(value) ((Py_ssize_t)_abs64(value))
-#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
- #define __Pyx_sst_abs(value) llabs(value)
-#elif defined (__GNUC__)
- #define __Pyx_sst_abs(value) __builtin_llabs(value)
-#else
- #define __Pyx_sst_abs(value) ((value<0) ? -value : value)
-#endif
-static CYTHON_INLINE const char* __Pyx_PyObject_AsString(PyObject*);
-static CYTHON_INLINE const char* __Pyx_PyObject_AsStringAndSize(PyObject*, Py_ssize_t* length);
-#define __Pyx_PyByteArray_FromString(s) PyByteArray_FromStringAndSize((const char*)s, strlen((const char*)s))
-#define __Pyx_PyByteArray_FromStringAndSize(s, l) PyByteArray_FromStringAndSize((const char*)s, l)
-#define __Pyx_PyBytes_FromString PyBytes_FromString
-#define __Pyx_PyBytes_FromStringAndSize PyBytes_FromStringAndSize
-static CYTHON_INLINE PyObject* __Pyx_PyUnicode_FromString(const char*);
-#if PY_MAJOR_VERSION < 3
- #define __Pyx_PyStr_FromString __Pyx_PyBytes_FromString
- #define __Pyx_PyStr_FromStringAndSize __Pyx_PyBytes_FromStringAndSize
-#else
- #define __Pyx_PyStr_FromString __Pyx_PyUnicode_FromString
- #define __Pyx_PyStr_FromStringAndSize __Pyx_PyUnicode_FromStringAndSize
-#endif
-#define __Pyx_PyBytes_AsWritableString(s) ((char*) PyBytes_AS_STRING(s))
-#define __Pyx_PyBytes_AsWritableSString(s) ((signed char*) PyBytes_AS_STRING(s))
-#define __Pyx_PyBytes_AsWritableUString(s) ((unsigned char*) PyBytes_AS_STRING(s))
-#define __Pyx_PyBytes_AsString(s) ((const char*) PyBytes_AS_STRING(s))
-#define __Pyx_PyBytes_AsSString(s) ((const signed char*) PyBytes_AS_STRING(s))
-#define __Pyx_PyBytes_AsUString(s) ((const unsigned char*) PyBytes_AS_STRING(s))
-#define __Pyx_PyObject_AsWritableString(s) ((char*) __Pyx_PyObject_AsString(s))
-#define __Pyx_PyObject_AsWritableSString(s) ((signed char*) __Pyx_PyObject_AsString(s))
-#define __Pyx_PyObject_AsWritableUString(s) ((unsigned char*) __Pyx_PyObject_AsString(s))
-#define __Pyx_PyObject_AsSString(s) ((const signed char*) __Pyx_PyObject_AsString(s))
-#define __Pyx_PyObject_AsUString(s) ((const unsigned char*) __Pyx_PyObject_AsString(s))
-#define __Pyx_PyObject_FromCString(s) __Pyx_PyObject_FromString((const char*)s)
-#define __Pyx_PyBytes_FromCString(s) __Pyx_PyBytes_FromString((const char*)s)
-#define __Pyx_PyByteArray_FromCString(s) __Pyx_PyByteArray_FromString((const char*)s)
-#define __Pyx_PyStr_FromCString(s) __Pyx_PyStr_FromString((const char*)s)
-#define __Pyx_PyUnicode_FromCString(s) __Pyx_PyUnicode_FromString((const char*)s)
-static CYTHON_INLINE size_t __Pyx_Py_UNICODE_strlen(const Py_UNICODE *u) {
- const Py_UNICODE *u_end = u;
- while (*u_end++) ;
- return (size_t)(u_end - u - 1);
-}
-#define __Pyx_PyUnicode_FromUnicode(u) PyUnicode_FromUnicode(u, __Pyx_Py_UNICODE_strlen(u))
-#define __Pyx_PyUnicode_FromUnicodeAndLength PyUnicode_FromUnicode
-#define __Pyx_PyUnicode_AsUnicode PyUnicode_AsUnicode
-#define __Pyx_NewRef(obj) (Py_INCREF(obj), obj)
-#define __Pyx_Owned_Py_None(b) __Pyx_NewRef(Py_None)
-static CYTHON_INLINE PyObject * __Pyx_PyBool_FromLong(long b);
-static CYTHON_INLINE int __Pyx_PyObject_IsTrue(PyObject*);
-static CYTHON_INLINE int __Pyx_PyObject_IsTrueAndDecref(PyObject*);
-static CYTHON_INLINE PyObject* __Pyx_PyNumber_IntOrLong(PyObject* x);
-#define __Pyx_PySequence_Tuple(obj)\
- (likely(PyTuple_CheckExact(obj)) ? __Pyx_NewRef(obj) : PySequence_Tuple(obj))
-static CYTHON_INLINE Py_ssize_t __Pyx_PyIndex_AsSsize_t(PyObject*);
-static CYTHON_INLINE PyObject * __Pyx_PyInt_FromSize_t(size_t);
-#if CYTHON_ASSUME_SAFE_MACROS
-#define __pyx_PyFloat_AsDouble(x) (PyFloat_CheckExact(x) ? PyFloat_AS_DOUBLE(x) : PyFloat_AsDouble(x))
-#else
-#define __pyx_PyFloat_AsDouble(x) PyFloat_AsDouble(x)
-#endif
-#define __pyx_PyFloat_AsFloat(x) ((float) __pyx_PyFloat_AsDouble(x))
-#if PY_MAJOR_VERSION >= 3
-#define __Pyx_PyNumber_Int(x) (PyLong_CheckExact(x) ? __Pyx_NewRef(x) : PyNumber_Long(x))
-#else
-#define __Pyx_PyNumber_Int(x) (PyInt_CheckExact(x) ? __Pyx_NewRef(x) : PyNumber_Int(x))
-#endif
-#define __Pyx_PyNumber_Float(x) (PyFloat_CheckExact(x) ? __Pyx_NewRef(x) : PyNumber_Float(x))
-#if PY_MAJOR_VERSION < 3 && __PYX_DEFAULT_STRING_ENCODING_IS_ASCII
-static int __Pyx_sys_getdefaultencoding_not_ascii;
-static int __Pyx_init_sys_getdefaultencoding_params(void) {
- PyObject* sys;
- PyObject* default_encoding = NULL;
- PyObject* ascii_chars_u = NULL;
- PyObject* ascii_chars_b = NULL;
- const char* default_encoding_c;
- sys = PyImport_ImportModule("sys");
- if (!sys) goto bad;
- default_encoding = PyObject_CallMethod(sys, (char*) "getdefaultencoding", NULL);
- Py_DECREF(sys);
- if (!default_encoding) goto bad;
- default_encoding_c = PyBytes_AsString(default_encoding);
- if (!default_encoding_c) goto bad;
- if (strcmp(default_encoding_c, "ascii") == 0) {
- __Pyx_sys_getdefaultencoding_not_ascii = 0;
- } else {
- char ascii_chars[128];
- int c;
- for (c = 0; c < 128; c++) {
- ascii_chars[c] = c;
- }
- __Pyx_sys_getdefaultencoding_not_ascii = 1;
- ascii_chars_u = PyUnicode_DecodeASCII(ascii_chars, 128, NULL);
- if (!ascii_chars_u) goto bad;
- ascii_chars_b = PyUnicode_AsEncodedString(ascii_chars_u, default_encoding_c, NULL);
- if (!ascii_chars_b || !PyBytes_Check(ascii_chars_b) || memcmp(ascii_chars, PyBytes_AS_STRING(ascii_chars_b), 128) != 0) {
- PyErr_Format(
- PyExc_ValueError,
- "This module compiled with c_string_encoding=ascii, but default encoding '%.200s' is not a superset of ascii.",
- default_encoding_c);
- goto bad;
- }
- Py_DECREF(ascii_chars_u);
- Py_DECREF(ascii_chars_b);
- }
- Py_DECREF(default_encoding);
- return 0;
-bad:
- Py_XDECREF(default_encoding);
- Py_XDECREF(ascii_chars_u);
- Py_XDECREF(ascii_chars_b);
- return -1;
-}
-#endif
-#if __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT && PY_MAJOR_VERSION >= 3
-#define __Pyx_PyUnicode_FromStringAndSize(c_str, size) PyUnicode_DecodeUTF8(c_str, size, NULL)
-#else
-#define __Pyx_PyUnicode_FromStringAndSize(c_str, size) PyUnicode_Decode(c_str, size, __PYX_DEFAULT_STRING_ENCODING, NULL)
-#if __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT
-static char* __PYX_DEFAULT_STRING_ENCODING;
-static int __Pyx_init_sys_getdefaultencoding_params(void) {
- PyObject* sys;
- PyObject* default_encoding = NULL;
- char* default_encoding_c;
- sys = PyImport_ImportModule("sys");
- if (!sys) goto bad;
- default_encoding = PyObject_CallMethod(sys, (char*) (const char*) "getdefaultencoding", NULL);
- Py_DECREF(sys);
- if (!default_encoding) goto bad;
- default_encoding_c = PyBytes_AsString(default_encoding);
- if (!default_encoding_c) goto bad;
- __PYX_DEFAULT_STRING_ENCODING = (char*) malloc(strlen(default_encoding_c) + 1);
- if (!__PYX_DEFAULT_STRING_ENCODING) goto bad;
- strcpy(__PYX_DEFAULT_STRING_ENCODING, default_encoding_c);
- Py_DECREF(default_encoding);
- return 0;
-bad:
- Py_XDECREF(default_encoding);
- return -1;
-}
-#endif
-#endif
-
-
-/* Test for GCC > 2.95 */
-#if defined(__GNUC__) && (__GNUC__ > 2 || (__GNUC__ == 2 && (__GNUC_MINOR__ > 95)))
- #define likely(x) __builtin_expect(!!(x), 1)
- #define unlikely(x) __builtin_expect(!!(x), 0)
-#else /* !__GNUC__ or GCC < 2.95 */
- #define likely(x) (x)
- #define unlikely(x) (x)
-#endif /* __GNUC__ */
-static CYTHON_INLINE void __Pyx_pretend_to_initialize(void* ptr) { (void)ptr; }
-
-static PyObject *__pyx_m = NULL;
-static PyObject *__pyx_d;
-static PyObject *__pyx_b;
-static PyObject *__pyx_cython_runtime = NULL;
-static PyObject *__pyx_empty_tuple;
-static PyObject *__pyx_empty_bytes;
-static PyObject *__pyx_empty_unicode;
-static int __pyx_lineno;
-static int __pyx_clineno = 0;
-static const char * __pyx_cfilenm= __FILE__;
-static const char *__pyx_filename;
-
-
-static const char *__pyx_f[] = {
- "silx/image/bilinear.pyx",
- "stringsource",
-};
-/* MemviewSliceStruct.proto */
-struct __pyx_memoryview_obj;
-typedef struct {
- struct __pyx_memoryview_obj *memview;
- char *data;
- Py_ssize_t shape[8];
- Py_ssize_t strides[8];
- Py_ssize_t suboffsets[8];
-} __Pyx_memviewslice;
-#define __Pyx_MemoryView_Len(m) (m.shape[0])
-
-/* Atomics.proto */
-#include <pythread.h>
-#ifndef CYTHON_ATOMICS
- #define CYTHON_ATOMICS 1
-#endif
-#define __pyx_atomic_int_type int
-#if CYTHON_ATOMICS && __GNUC__ >= 4 && (__GNUC_MINOR__ > 1 ||\
- (__GNUC_MINOR__ == 1 && __GNUC_PATCHLEVEL >= 2)) &&\
- !defined(__i386__)
- #define __pyx_atomic_incr_aligned(value, lock) __sync_fetch_and_add(value, 1)
- #define __pyx_atomic_decr_aligned(value, lock) __sync_fetch_and_sub(value, 1)
- #ifdef __PYX_DEBUG_ATOMICS
- #warning "Using GNU atomics"
- #endif
-#elif CYTHON_ATOMICS && defined(_MSC_VER) && 0
- #include <Windows.h>
- #undef __pyx_atomic_int_type
- #define __pyx_atomic_int_type LONG
- #define __pyx_atomic_incr_aligned(value, lock) InterlockedIncrement(value)
- #define __pyx_atomic_decr_aligned(value, lock) InterlockedDecrement(value)
- #ifdef __PYX_DEBUG_ATOMICS
- #pragma message ("Using MSVC atomics")
- #endif
-#elif CYTHON_ATOMICS && (defined(__ICC) || defined(__INTEL_COMPILER)) && 0
- #define __pyx_atomic_incr_aligned(value, lock) _InterlockedIncrement(value)
- #define __pyx_atomic_decr_aligned(value, lock) _InterlockedDecrement(value)
- #ifdef __PYX_DEBUG_ATOMICS
- #warning "Using Intel atomics"
- #endif
-#else
- #undef CYTHON_ATOMICS
- #define CYTHON_ATOMICS 0
- #ifdef __PYX_DEBUG_ATOMICS
- #warning "Not using atomics"
- #endif
-#endif
-typedef volatile __pyx_atomic_int_type __pyx_atomic_int;
-#if CYTHON_ATOMICS
- #define __pyx_add_acquisition_count(memview)\
- __pyx_atomic_incr_aligned(__pyx_get_slice_count_pointer(memview), memview->lock)
- #define __pyx_sub_acquisition_count(memview)\
- __pyx_atomic_decr_aligned(__pyx_get_slice_count_pointer(memview), memview->lock)
-#else
- #define __pyx_add_acquisition_count(memview)\
- __pyx_add_acquisition_count_locked(__pyx_get_slice_count_pointer(memview), memview->lock)
- #define __pyx_sub_acquisition_count(memview)\
- __pyx_sub_acquisition_count_locked(__pyx_get_slice_count_pointer(memview), memview->lock)
-#endif
-
-/* NoFastGil.proto */
-#define __Pyx_PyGILState_Ensure PyGILState_Ensure
-#define __Pyx_PyGILState_Release PyGILState_Release
-#define __Pyx_FastGIL_Remember()
-#define __Pyx_FastGIL_Forget()
-#define __Pyx_FastGilFuncInit()
-
-/* ForceInitThreads.proto */
-#ifndef __PYX_FORCE_INIT_THREADS
- #define __PYX_FORCE_INIT_THREADS 0
-#endif
-
-/* BufferFormatStructs.proto */
-#define IS_UNSIGNED(type) (((type) -1) > 0)
-struct __Pyx_StructField_;
-#define __PYX_BUF_FLAGS_PACKED_STRUCT (1 << 0)
-typedef struct {
- const char* name;
- struct __Pyx_StructField_* fields;
- size_t size;
- size_t arraysize[8];
- int ndim;
- char typegroup;
- char is_unsigned;
- int flags;
-} __Pyx_TypeInfo;
-typedef struct __Pyx_StructField_ {
- __Pyx_TypeInfo* type;
- const char* name;
- size_t offset;
-} __Pyx_StructField;
-typedef struct {
- __Pyx_StructField* field;
- size_t parent_offset;
-} __Pyx_BufFmt_StackElem;
-typedef struct {
- __Pyx_StructField root;
- __Pyx_BufFmt_StackElem* head;
- size_t fmt_offset;
- size_t new_count, enc_count;
- size_t struct_alignment;
- int is_complex;
- char enc_type;
- char new_packmode;
- char enc_packmode;
- char is_valid_array;
-} __Pyx_BufFmt_Context;
-
-
-/*--- Type declarations ---*/
-struct __pyx_obj_4silx_5image_8bilinear_BilinearImage;
-struct __pyx_array_obj;
-struct __pyx_MemviewEnum_obj;
-struct __pyx_memoryview_obj;
-struct __pyx_memoryviewslice_obj;
-
-/* "silx/image/bilinear.pyx":39
- *
- *
- * cdef class BilinearImage: # <<<<<<<<<<<<<<
- * """Bilinear interpolator for images ... or any data on a regular grid
- * """
- */
-struct __pyx_obj_4silx_5image_8bilinear_BilinearImage {
- PyObject_HEAD
- struct __pyx_vtabstruct_4silx_5image_8bilinear_BilinearImage *__pyx_vtab;
- __Pyx_memviewslice data;
- float maxi;
- float mini;
- size_t width;
- size_t height;
-};
-
-
-/* "View.MemoryView":105
- *
- * @cname("__pyx_array")
- * cdef class array: # <<<<<<<<<<<<<<
- *
- * cdef:
- */
-struct __pyx_array_obj {
- PyObject_HEAD
- struct __pyx_vtabstruct_array *__pyx_vtab;
- char *data;
- Py_ssize_t len;
- char *format;
- int ndim;
- Py_ssize_t *_shape;
- Py_ssize_t *_strides;
- Py_ssize_t itemsize;
- PyObject *mode;
- PyObject *_format;
- void (*callback_free_data)(void *);
- int free_data;
- int dtype_is_object;
-};
-
-
-/* "View.MemoryView":279
- *
- * @cname('__pyx_MemviewEnum')
- * cdef class Enum(object): # <<<<<<<<<<<<<<
- * cdef object name
- * def __init__(self, name):
- */
-struct __pyx_MemviewEnum_obj {
- PyObject_HEAD
- PyObject *name;
-};
-
-
-/* "View.MemoryView":330
- *
- * @cname('__pyx_memoryview')
- * cdef class memoryview(object): # <<<<<<<<<<<<<<
- *
- * cdef object obj
- */
-struct __pyx_memoryview_obj {
- PyObject_HEAD
- struct __pyx_vtabstruct_memoryview *__pyx_vtab;
- PyObject *obj;
- PyObject *_size;
- PyObject *_array_interface;
- PyThread_type_lock lock;
- __pyx_atomic_int acquisition_count[2];
- __pyx_atomic_int *acquisition_count_aligned_p;
- Py_buffer view;
- int flags;
- int dtype_is_object;
- __Pyx_TypeInfo *typeinfo;
-};
-
-
-/* "View.MemoryView":961
- *
- * @cname('__pyx_memoryviewslice')
- * cdef class _memoryviewslice(memoryview): # <<<<<<<<<<<<<<
- * "Internal class for passing memoryview slices to Python"
- *
- */
-struct __pyx_memoryviewslice_obj {
- struct __pyx_memoryview_obj __pyx_base;
- __Pyx_memviewslice from_slice;
- PyObject *from_object;
- PyObject *(*to_object_func)(char *);
- int (*to_dtype_func)(char *, PyObject *);
-};
-
-
-
-/* "View.MemoryView":105
- *
- * @cname("__pyx_array")
- * cdef class array: # <<<<<<<<<<<<<<
- *
- * cdef:
- */
-
-struct __pyx_vtabstruct_array {
- PyObject *(*get_memview)(struct __pyx_array_obj *);
-};
-static struct __pyx_vtabstruct_array *__pyx_vtabptr_array;
-
-
-/* "silx/image/bilinear.pyx":39
- *
- *
- * cdef class BilinearImage: # <<<<<<<<<<<<<<
- * """Bilinear interpolator for images ... or any data on a regular grid
- * """
- */
-
-struct __pyx_vtabstruct_4silx_5image_8bilinear_BilinearImage {
- size_t (*coarse_local_maxi)(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *, size_t, int __pyx_skip_dispatch);
- size_t (*c_local_maxi)(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *, size_t);
- float (*c_funct)(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *, float, float);
-};
-static struct __pyx_vtabstruct_4silx_5image_8bilinear_BilinearImage *__pyx_vtabptr_4silx_5image_8bilinear_BilinearImage;
-
-
-/* "View.MemoryView":330
- *
- * @cname('__pyx_memoryview')
- * cdef class memoryview(object): # <<<<<<<<<<<<<<
- *
- * cdef object obj
- */
-
-struct __pyx_vtabstruct_memoryview {
- char *(*get_item_pointer)(struct __pyx_memoryview_obj *, PyObject *);
- PyObject *(*is_slice)(struct __pyx_memoryview_obj *, PyObject *);
- PyObject *(*setitem_slice_assignment)(struct __pyx_memoryview_obj *, PyObject *, PyObject *);
- PyObject *(*setitem_slice_assign_scalar)(struct __pyx_memoryview_obj *, struct __pyx_memoryview_obj *, PyObject *);
- PyObject *(*setitem_indexed)(struct __pyx_memoryview_obj *, PyObject *, PyObject *);
- PyObject *(*convert_item_to_object)(struct __pyx_memoryview_obj *, char *);
- PyObject *(*assign_item_from_object)(struct __pyx_memoryview_obj *, char *, PyObject *);
-};
-static struct __pyx_vtabstruct_memoryview *__pyx_vtabptr_memoryview;
-
-
-/* "View.MemoryView":961
- *
- * @cname('__pyx_memoryviewslice')
- * cdef class _memoryviewslice(memoryview): # <<<<<<<<<<<<<<
- * "Internal class for passing memoryview slices to Python"
- *
- */
-
-struct __pyx_vtabstruct__memoryviewslice {
- struct __pyx_vtabstruct_memoryview __pyx_base;
-};
-static struct __pyx_vtabstruct__memoryviewslice *__pyx_vtabptr__memoryviewslice;
-
-/* --- Runtime support code (head) --- */
-/* Refnanny.proto */
-#ifndef CYTHON_REFNANNY
- #define CYTHON_REFNANNY 0
-#endif
-#if CYTHON_REFNANNY
- typedef struct {
- void (*INCREF)(void*, PyObject*, int);
- void (*DECREF)(void*, PyObject*, int);
- void (*GOTREF)(void*, PyObject*, int);
- void (*GIVEREF)(void*, PyObject*, int);
- void* (*SetupContext)(const char*, int, const char*);
- void (*FinishContext)(void**);
- } __Pyx_RefNannyAPIStruct;
- static __Pyx_RefNannyAPIStruct *__Pyx_RefNanny = NULL;
- static __Pyx_RefNannyAPIStruct *__Pyx_RefNannyImportAPI(const char *modname);
- #define __Pyx_RefNannyDeclarations void *__pyx_refnanny = NULL;
-#ifdef WITH_THREAD
- #define __Pyx_RefNannySetupContext(name, acquire_gil)\
- if (acquire_gil) {\
- PyGILState_STATE __pyx_gilstate_save = PyGILState_Ensure();\
- __pyx_refnanny = __Pyx_RefNanny->SetupContext((name), __LINE__, __FILE__);\
- PyGILState_Release(__pyx_gilstate_save);\
- } else {\
- __pyx_refnanny = __Pyx_RefNanny->SetupContext((name), __LINE__, __FILE__);\
- }
-#else
- #define __Pyx_RefNannySetupContext(name, acquire_gil)\
- __pyx_refnanny = __Pyx_RefNanny->SetupContext((name), __LINE__, __FILE__)
-#endif
- #define __Pyx_RefNannyFinishContext()\
- __Pyx_RefNanny->FinishContext(&__pyx_refnanny)
- #define __Pyx_INCREF(r) __Pyx_RefNanny->INCREF(__pyx_refnanny, (PyObject *)(r), __LINE__)
- #define __Pyx_DECREF(r) __Pyx_RefNanny->DECREF(__pyx_refnanny, (PyObject *)(r), __LINE__)
- #define __Pyx_GOTREF(r) __Pyx_RefNanny->GOTREF(__pyx_refnanny, (PyObject *)(r), __LINE__)
- #define __Pyx_GIVEREF(r) __Pyx_RefNanny->GIVEREF(__pyx_refnanny, (PyObject *)(r), __LINE__)
- #define __Pyx_XINCREF(r) do { if((r) != NULL) {__Pyx_INCREF(r); }} while(0)
- #define __Pyx_XDECREF(r) do { if((r) != NULL) {__Pyx_DECREF(r); }} while(0)
- #define __Pyx_XGOTREF(r) do { if((r) != NULL) {__Pyx_GOTREF(r); }} while(0)
- #define __Pyx_XGIVEREF(r) do { if((r) != NULL) {__Pyx_GIVEREF(r);}} while(0)
-#else
- #define __Pyx_RefNannyDeclarations
- #define __Pyx_RefNannySetupContext(name, acquire_gil)
- #define __Pyx_RefNannyFinishContext()
- #define __Pyx_INCREF(r) Py_INCREF(r)
- #define __Pyx_DECREF(r) Py_DECREF(r)
- #define __Pyx_GOTREF(r)
- #define __Pyx_GIVEREF(r)
- #define __Pyx_XINCREF(r) Py_XINCREF(r)
- #define __Pyx_XDECREF(r) Py_XDECREF(r)
- #define __Pyx_XGOTREF(r)
- #define __Pyx_XGIVEREF(r)
-#endif
-#define __Pyx_XDECREF_SET(r, v) do {\
- PyObject *tmp = (PyObject *) r;\
- r = v; __Pyx_XDECREF(tmp);\
- } while (0)
-#define __Pyx_DECREF_SET(r, v) do {\
- PyObject *tmp = (PyObject *) r;\
- r = v; __Pyx_DECREF(tmp);\
- } while (0)
-#define __Pyx_CLEAR(r) do { PyObject* tmp = ((PyObject*)(r)); r = NULL; __Pyx_DECREF(tmp);} while(0)
-#define __Pyx_XCLEAR(r) do { if((r) != NULL) {PyObject* tmp = ((PyObject*)(r)); r = NULL; __Pyx_DECREF(tmp);}} while(0)
-
-/* PyObjectGetAttrStr.proto */
-#if CYTHON_USE_TYPE_SLOTS
-static CYTHON_INLINE PyObject* __Pyx_PyObject_GetAttrStr(PyObject* obj, PyObject* attr_name);
-#else
-#define __Pyx_PyObject_GetAttrStr(o,n) PyObject_GetAttr(o,n)
-#endif
-
-/* GetBuiltinName.proto */
-static PyObject *__Pyx_GetBuiltinName(PyObject *name);
-
-/* RaiseDoubleKeywords.proto */
-static void __Pyx_RaiseDoubleKeywordsError(const char* func_name, PyObject* kw_name);
-
-/* ParseKeywords.proto */
-static int __Pyx_ParseOptionalKeywords(PyObject *kwds, PyObject **argnames[],\
- PyObject *kwds2, PyObject *values[], Py_ssize_t num_pos_args,\
- const char* function_name);
-
-/* RaiseArgTupleInvalid.proto */
-static void __Pyx_RaiseArgtupleInvalid(const char* func_name, int exact,
- Py_ssize_t num_min, Py_ssize_t num_max, Py_ssize_t num_found);
-
-/* PyIntCompare.proto */
-static CYTHON_INLINE PyObject* __Pyx_PyInt_EqObjC(PyObject *op1, PyObject *op2, long intval, long inplace);
-
-/* GetItemInt.proto */
-#define __Pyx_GetItemInt(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck)\
- (__Pyx_fits_Py_ssize_t(i, type, is_signed) ?\
- __Pyx_GetItemInt_Fast(o, (Py_ssize_t)i, is_list, wraparound, boundscheck) :\
- (is_list ? (PyErr_SetString(PyExc_IndexError, "list index out of range"), (PyObject*)NULL) :\
- __Pyx_GetItemInt_Generic(o, to_py_func(i))))
-#define __Pyx_GetItemInt_List(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck)\
- (__Pyx_fits_Py_ssize_t(i, type, is_signed) ?\
- __Pyx_GetItemInt_List_Fast(o, (Py_ssize_t)i, wraparound, boundscheck) :\
- (PyErr_SetString(PyExc_IndexError, "list index out of range"), (PyObject*)NULL))
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_t i,
- int wraparound, int boundscheck);
-#define __Pyx_GetItemInt_Tuple(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck)\
- (__Pyx_fits_Py_ssize_t(i, type, is_signed) ?\
- __Pyx_GetItemInt_Tuple_Fast(o, (Py_ssize_t)i, wraparound, boundscheck) :\
- (PyErr_SetString(PyExc_IndexError, "tuple index out of range"), (PyObject*)NULL))
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize_t i,
- int wraparound, int boundscheck);
-static PyObject *__Pyx_GetItemInt_Generic(PyObject *o, PyObject* j);
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i,
- int is_list, int wraparound, int boundscheck);
-
-/* PyFunctionFastCall.proto */
-#if CYTHON_FAST_PYCALL
-#define __Pyx_PyFunction_FastCall(func, args, nargs)\
- __Pyx_PyFunction_FastCallDict((func), (args), (nargs), NULL)
-#if 1 || PY_VERSION_HEX < 0x030600B1
-static PyObject *__Pyx_PyFunction_FastCallDict(PyObject *func, PyObject **args, int nargs, PyObject *kwargs);
-#else
-#define __Pyx_PyFunction_FastCallDict(func, args, nargs, kwargs) _PyFunction_FastCallDict(func, args, nargs, kwargs)
-#endif
-#define __Pyx_BUILD_ASSERT_EXPR(cond)\
- (sizeof(char [1 - 2*!(cond)]) - 1)
-#ifndef Py_MEMBER_SIZE
-#define Py_MEMBER_SIZE(type, member) sizeof(((type *)0)->member)
-#endif
- static size_t __pyx_pyframe_localsplus_offset = 0;
- #include "frameobject.h"
- #define __Pxy_PyFrame_Initialize_Offsets()\
- ((void)__Pyx_BUILD_ASSERT_EXPR(sizeof(PyFrameObject) == offsetof(PyFrameObject, f_localsplus) + Py_MEMBER_SIZE(PyFrameObject, f_localsplus)),\
- (void)(__pyx_pyframe_localsplus_offset = ((size_t)PyFrame_Type.tp_basicsize) - Py_MEMBER_SIZE(PyFrameObject, f_localsplus)))
- #define __Pyx_PyFrame_GetLocalsplus(frame)\
- (assert(__pyx_pyframe_localsplus_offset), (PyObject **)(((char *)(frame)) + __pyx_pyframe_localsplus_offset))
-#endif
-
-/* PyObjectCall.proto */
-#if CYTHON_COMPILING_IN_CPYTHON
-static CYTHON_INLINE PyObject* __Pyx_PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw);
-#else
-#define __Pyx_PyObject_Call(func, arg, kw) PyObject_Call(func, arg, kw)
-#endif
-
-/* PyObjectCallMethO.proto */
-#if CYTHON_COMPILING_IN_CPYTHON
-static CYTHON_INLINE PyObject* __Pyx_PyObject_CallMethO(PyObject *func, PyObject *arg);
-#endif
-
-/* PyObjectCallNoArg.proto */
-#if CYTHON_COMPILING_IN_CPYTHON
-static CYTHON_INLINE PyObject* __Pyx_PyObject_CallNoArg(PyObject *func);
-#else
-#define __Pyx_PyObject_CallNoArg(func) __Pyx_PyObject_Call(func, __pyx_empty_tuple, NULL)
-#endif
-
-/* PyCFunctionFastCall.proto */
-#if CYTHON_FAST_PYCCALL
-static CYTHON_INLINE PyObject *__Pyx_PyCFunction_FastCall(PyObject *func, PyObject **args, Py_ssize_t nargs);
-#else
-#define __Pyx_PyCFunction_FastCall(func, args, nargs) (assert(0), NULL)
-#endif
-
-/* PyObjectCallOneArg.proto */
-static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObject *arg);
-
-/* PyDictVersioning.proto */
-#if CYTHON_USE_DICT_VERSIONS && CYTHON_USE_TYPE_SLOTS
-#define __PYX_DICT_VERSION_INIT ((PY_UINT64_T) -1)
-#define __PYX_GET_DICT_VERSION(dict) (((PyDictObject*)(dict))->ma_version_tag)
-#define __PYX_UPDATE_DICT_CACHE(dict, value, cache_var, version_var)\
- (version_var) = __PYX_GET_DICT_VERSION(dict);\
- (cache_var) = (value);
-#define __PYX_PY_DICT_LOOKUP_IF_MODIFIED(VAR, DICT, LOOKUP) {\
- static PY_UINT64_T __pyx_dict_version = 0;\
- static PyObject *__pyx_dict_cached_value = NULL;\
- if (likely(__PYX_GET_DICT_VERSION(DICT) == __pyx_dict_version)) {\
- (VAR) = __pyx_dict_cached_value;\
- } else {\
- (VAR) = __pyx_dict_cached_value = (LOOKUP);\
- __pyx_dict_version = __PYX_GET_DICT_VERSION(DICT);\
- }\
-}
-static CYTHON_INLINE PY_UINT64_T __Pyx_get_tp_dict_version(PyObject *obj);
-static CYTHON_INLINE PY_UINT64_T __Pyx_get_object_dict_version(PyObject *obj);
-static CYTHON_INLINE int __Pyx_object_dict_version_matches(PyObject* obj, PY_UINT64_T tp_dict_version, PY_UINT64_T obj_dict_version);
-#else
-#define __PYX_GET_DICT_VERSION(dict) (0)
-#define __PYX_UPDATE_DICT_CACHE(dict, value, cache_var, version_var)
-#define __PYX_PY_DICT_LOOKUP_IF_MODIFIED(VAR, DICT, LOOKUP) (VAR) = (LOOKUP);
-#endif
-
-/* GetModuleGlobalName.proto */
-#if CYTHON_USE_DICT_VERSIONS
-#define __Pyx_GetModuleGlobalName(var, name) {\
- static PY_UINT64_T __pyx_dict_version = 0;\
- static PyObject *__pyx_dict_cached_value = NULL;\
- (var) = (likely(__pyx_dict_version == __PYX_GET_DICT_VERSION(__pyx_d))) ?\
- (likely(__pyx_dict_cached_value) ? __Pyx_NewRef(__pyx_dict_cached_value) : __Pyx_GetBuiltinName(name)) :\
- __Pyx__GetModuleGlobalName(name, &__pyx_dict_version, &__pyx_dict_cached_value);\
-}
-#define __Pyx_GetModuleGlobalNameUncached(var, name) {\
- PY_UINT64_T __pyx_dict_version;\
- PyObject *__pyx_dict_cached_value;\
- (var) = __Pyx__GetModuleGlobalName(name, &__pyx_dict_version, &__pyx_dict_cached_value);\
-}
-static PyObject *__Pyx__GetModuleGlobalName(PyObject *name, PY_UINT64_T *dict_version, PyObject **dict_cached_value);
-#else
-#define __Pyx_GetModuleGlobalName(var, name) (var) = __Pyx__GetModuleGlobalName(name)
-#define __Pyx_GetModuleGlobalNameUncached(var, name) (var) = __Pyx__GetModuleGlobalName(name)
-static CYTHON_INLINE PyObject *__Pyx__GetModuleGlobalName(PyObject *name);
-#endif
-
-/* MemviewSliceInit.proto */
-#define __Pyx_BUF_MAX_NDIMS %(BUF_MAX_NDIMS)d
-#define __Pyx_MEMVIEW_DIRECT 1
-#define __Pyx_MEMVIEW_PTR 2
-#define __Pyx_MEMVIEW_FULL 4
-#define __Pyx_MEMVIEW_CONTIG 8
-#define __Pyx_MEMVIEW_STRIDED 16
-#define __Pyx_MEMVIEW_FOLLOW 32
-#define __Pyx_IS_C_CONTIG 1
-#define __Pyx_IS_F_CONTIG 2
-static int __Pyx_init_memviewslice(
- struct __pyx_memoryview_obj *memview,
- int ndim,
- __Pyx_memviewslice *memviewslice,
- int memview_is_new_reference);
-static CYTHON_INLINE int __pyx_add_acquisition_count_locked(
- __pyx_atomic_int *acquisition_count, PyThread_type_lock lock);
-static CYTHON_INLINE int __pyx_sub_acquisition_count_locked(
- __pyx_atomic_int *acquisition_count, PyThread_type_lock lock);
-#define __pyx_get_slice_count_pointer(memview) (memview->acquisition_count_aligned_p)
-#define __pyx_get_slice_count(memview) (*__pyx_get_slice_count_pointer(memview))
-#define __PYX_INC_MEMVIEW(slice, have_gil) __Pyx_INC_MEMVIEW(slice, have_gil, __LINE__)
-#define __PYX_XDEC_MEMVIEW(slice, have_gil) __Pyx_XDEC_MEMVIEW(slice, have_gil, __LINE__)
-static CYTHON_INLINE void __Pyx_INC_MEMVIEW(__Pyx_memviewslice *, int, int);
-static CYTHON_INLINE void __Pyx_XDEC_MEMVIEW(__Pyx_memviewslice *, int, int);
-
-/* PyThreadStateGet.proto */
-#if CYTHON_FAST_THREAD_STATE
-#define __Pyx_PyThreadState_declare PyThreadState *__pyx_tstate;
-#define __Pyx_PyThreadState_assign __pyx_tstate = __Pyx_PyThreadState_Current;
-#define __Pyx_PyErr_Occurred() __pyx_tstate->curexc_type
-#else
-#define __Pyx_PyThreadState_declare
-#define __Pyx_PyThreadState_assign
-#define __Pyx_PyErr_Occurred() PyErr_Occurred()
-#endif
-
-/* PyErrFetchRestore.proto */
-#if CYTHON_FAST_THREAD_STATE
-#define __Pyx_PyErr_Clear() __Pyx_ErrRestore(NULL, NULL, NULL)
-#define __Pyx_ErrRestoreWithState(type, value, tb) __Pyx_ErrRestoreInState(PyThreadState_GET(), type, value, tb)
-#define __Pyx_ErrFetchWithState(type, value, tb) __Pyx_ErrFetchInState(PyThreadState_GET(), type, value, tb)
-#define __Pyx_ErrRestore(type, value, tb) __Pyx_ErrRestoreInState(__pyx_tstate, type, value, tb)
-#define __Pyx_ErrFetch(type, value, tb) __Pyx_ErrFetchInState(__pyx_tstate, type, value, tb)
-static CYTHON_INLINE void __Pyx_ErrRestoreInState(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *tb);
-static CYTHON_INLINE void __Pyx_ErrFetchInState(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb);
-#if CYTHON_COMPILING_IN_CPYTHON
-#define __Pyx_PyErr_SetNone(exc) (Py_INCREF(exc), __Pyx_ErrRestore((exc), NULL, NULL))
-#else
-#define __Pyx_PyErr_SetNone(exc) PyErr_SetNone(exc)
-#endif
-#else
-#define __Pyx_PyErr_Clear() PyErr_Clear()
-#define __Pyx_PyErr_SetNone(exc) PyErr_SetNone(exc)
-#define __Pyx_ErrRestoreWithState(type, value, tb) PyErr_Restore(type, value, tb)
-#define __Pyx_ErrFetchWithState(type, value, tb) PyErr_Fetch(type, value, tb)
-#define __Pyx_ErrRestoreInState(tstate, type, value, tb) PyErr_Restore(type, value, tb)
-#define __Pyx_ErrFetchInState(tstate, type, value, tb) PyErr_Fetch(type, value, tb)
-#define __Pyx_ErrRestore(type, value, tb) PyErr_Restore(type, value, tb)
-#define __Pyx_ErrFetch(type, value, tb) PyErr_Fetch(type, value, tb)
-#endif
-
-/* WriteUnraisableException.proto */
-static void __Pyx_WriteUnraisable(const char *name, int clineno,
- int lineno, const char *filename,
- int full_traceback, int nogil);
-
-/* RaiseTooManyValuesToUnpack.proto */
-static CYTHON_INLINE void __Pyx_RaiseTooManyValuesError(Py_ssize_t expected);
-
-/* RaiseNeedMoreValuesToUnpack.proto */
-static CYTHON_INLINE void __Pyx_RaiseNeedMoreValuesError(Py_ssize_t index);
-
-/* IterFinish.proto */
-static CYTHON_INLINE int __Pyx_IterFinish(void);
-
-/* UnpackItemEndCheck.proto */
-static int __Pyx_IternextUnpackEndCheck(PyObject *retval, Py_ssize_t expected);
-
-/* PyObjectCall2Args.proto */
-static CYTHON_UNUSED PyObject* __Pyx_PyObject_Call2Args(PyObject* function, PyObject* arg1, PyObject* arg2);
-
-/* RaiseException.proto */
-static void __Pyx_Raise(PyObject *type, PyObject *value, PyObject *tb, PyObject *cause);
-
-/* ArgTypeTest.proto */
-#define __Pyx_ArgTypeTest(obj, type, none_allowed, name, exact)\
- ((likely((Py_TYPE(obj) == type) | (none_allowed && (obj == Py_None)))) ? 1 :\
- __Pyx__ArgTypeTest(obj, type, name, exact))
-static int __Pyx__ArgTypeTest(PyObject *obj, PyTypeObject *type, const char *name, int exact);
-
-/* IncludeStringH.proto */
-#include <string.h>
-
-/* BytesEquals.proto */
-static CYTHON_INLINE int __Pyx_PyBytes_Equals(PyObject* s1, PyObject* s2, int equals);
-
-/* UnicodeEquals.proto */
-static CYTHON_INLINE int __Pyx_PyUnicode_Equals(PyObject* s1, PyObject* s2, int equals);
-
-/* StrEquals.proto */
-#if PY_MAJOR_VERSION >= 3
-#define __Pyx_PyString_Equals __Pyx_PyUnicode_Equals
-#else
-#define __Pyx_PyString_Equals __Pyx_PyBytes_Equals
-#endif
-
-/* None.proto */
-static CYTHON_INLINE Py_ssize_t __Pyx_div_Py_ssize_t(Py_ssize_t, Py_ssize_t);
-
-/* UnaryNegOverflows.proto */
-#define UNARY_NEG_WOULD_OVERFLOW(x)\
- (((x) < 0) & ((unsigned long)(x) == 0-(unsigned long)(x)))
-
-static CYTHON_UNUSED int __pyx_array_getbuffer(PyObject *__pyx_v_self, Py_buffer *__pyx_v_info, int __pyx_v_flags); /*proto*/
-static PyObject *__pyx_array_get_memview(struct __pyx_array_obj *); /*proto*/
-/* GetAttr.proto */
-static CYTHON_INLINE PyObject *__Pyx_GetAttr(PyObject *, PyObject *);
-
-/* ObjectGetItem.proto */
-#if CYTHON_USE_TYPE_SLOTS
-static CYTHON_INLINE PyObject *__Pyx_PyObject_GetItem(PyObject *obj, PyObject* key);
-#else
-#define __Pyx_PyObject_GetItem(obj, key) PyObject_GetItem(obj, key)
-#endif
-
-/* decode_c_string_utf16.proto */
-static CYTHON_INLINE PyObject *__Pyx_PyUnicode_DecodeUTF16(const char *s, Py_ssize_t size, const char *errors) {
- int byteorder = 0;
- return PyUnicode_DecodeUTF16(s, size, errors, &byteorder);
-}
-static CYTHON_INLINE PyObject *__Pyx_PyUnicode_DecodeUTF16LE(const char *s, Py_ssize_t size, const char *errors) {
- int byteorder = -1;
- return PyUnicode_DecodeUTF16(s, size, errors, &byteorder);
-}
-static CYTHON_INLINE PyObject *__Pyx_PyUnicode_DecodeUTF16BE(const char *s, Py_ssize_t size, const char *errors) {
- int byteorder = 1;
- return PyUnicode_DecodeUTF16(s, size, errors, &byteorder);
-}
-
-/* decode_c_string.proto */
-static CYTHON_INLINE PyObject* __Pyx_decode_c_string(
- const char* cstring, Py_ssize_t start, Py_ssize_t stop,
- const char* encoding, const char* errors,
- PyObject* (*decode_func)(const char *s, Py_ssize_t size, const char *errors));
-
-/* PyErrExceptionMatches.proto */
-#if CYTHON_FAST_THREAD_STATE
-#define __Pyx_PyErr_ExceptionMatches(err) __Pyx_PyErr_ExceptionMatchesInState(__pyx_tstate, err)
-static CYTHON_INLINE int __Pyx_PyErr_ExceptionMatchesInState(PyThreadState* tstate, PyObject* err);
-#else
-#define __Pyx_PyErr_ExceptionMatches(err) PyErr_ExceptionMatches(err)
-#endif
-
-/* GetAttr3.proto */
-static CYTHON_INLINE PyObject *__Pyx_GetAttr3(PyObject *, PyObject *, PyObject *);
-
-/* RaiseNoneIterError.proto */
-static CYTHON_INLINE void __Pyx_RaiseNoneNotIterableError(void);
-
-/* ExtTypeTest.proto */
-static CYTHON_INLINE int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type);
-
-/* GetTopmostException.proto */
-#if CYTHON_USE_EXC_INFO_STACK
-static _PyErr_StackItem * __Pyx_PyErr_GetTopmostException(PyThreadState *tstate);
-#endif
-
-/* SaveResetException.proto */
-#if CYTHON_FAST_THREAD_STATE
-#define __Pyx_ExceptionSave(type, value, tb) __Pyx__ExceptionSave(__pyx_tstate, type, value, tb)
-static CYTHON_INLINE void __Pyx__ExceptionSave(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb);
-#define __Pyx_ExceptionReset(type, value, tb) __Pyx__ExceptionReset(__pyx_tstate, type, value, tb)
-static CYTHON_INLINE void __Pyx__ExceptionReset(PyThreadState *tstate, PyObject *type, PyObject *value, PyObject *tb);
-#else
-#define __Pyx_ExceptionSave(type, value, tb) PyErr_GetExcInfo(type, value, tb)
-#define __Pyx_ExceptionReset(type, value, tb) PyErr_SetExcInfo(type, value, tb)
-#endif
-
-/* GetException.proto */
-#if CYTHON_FAST_THREAD_STATE
-#define __Pyx_GetException(type, value, tb) __Pyx__GetException(__pyx_tstate, type, value, tb)
-static int __Pyx__GetException(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb);
-#else
-static int __Pyx_GetException(PyObject **type, PyObject **value, PyObject **tb);
-#endif
-
-/* SwapException.proto */
-#if CYTHON_FAST_THREAD_STATE
-#define __Pyx_ExceptionSwap(type, value, tb) __Pyx__ExceptionSwap(__pyx_tstate, type, value, tb)
-static CYTHON_INLINE void __Pyx__ExceptionSwap(PyThreadState *tstate, PyObject **type, PyObject **value, PyObject **tb);
-#else
-static CYTHON_INLINE void __Pyx_ExceptionSwap(PyObject **type, PyObject **value, PyObject **tb);
-#endif
-
-/* Import.proto */
-static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list, int level);
-
-/* FastTypeChecks.proto */
-#if CYTHON_COMPILING_IN_CPYTHON
-#define __Pyx_TypeCheck(obj, type) __Pyx_IsSubtype(Py_TYPE(obj), (PyTypeObject *)type)
-static CYTHON_INLINE int __Pyx_IsSubtype(PyTypeObject *a, PyTypeObject *b);
-static CYTHON_INLINE int __Pyx_PyErr_GivenExceptionMatches(PyObject *err, PyObject *type);
-static CYTHON_INLINE int __Pyx_PyErr_GivenExceptionMatches2(PyObject *err, PyObject *type1, PyObject *type2);
-#else
-#define __Pyx_TypeCheck(obj, type) PyObject_TypeCheck(obj, (PyTypeObject *)type)
-#define __Pyx_PyErr_GivenExceptionMatches(err, type) PyErr_GivenExceptionMatches(err, type)
-#define __Pyx_PyErr_GivenExceptionMatches2(err, type1, type2) (PyErr_GivenExceptionMatches(err, type1) || PyErr_GivenExceptionMatches(err, type2))
-#endif
-#define __Pyx_PyException_Check(obj) __Pyx_TypeCheck(obj, PyExc_Exception)
-
-static CYTHON_UNUSED int __pyx_memoryview_getbuffer(PyObject *__pyx_v_self, Py_buffer *__pyx_v_info, int __pyx_v_flags); /*proto*/
-/* ListCompAppend.proto */
-#if CYTHON_USE_PYLIST_INTERNALS && CYTHON_ASSUME_SAFE_MACROS
-static CYTHON_INLINE int __Pyx_ListComp_Append(PyObject* list, PyObject* x) {
- PyListObject* L = (PyListObject*) list;
- Py_ssize_t len = Py_SIZE(list);
- if (likely(L->allocated > len)) {
- Py_INCREF(x);
- PyList_SET_ITEM(list, len, x);
- Py_SIZE(list) = len+1;
- return 0;
- }
- return PyList_Append(list, x);
-}
-#else
-#define __Pyx_ListComp_Append(L,x) PyList_Append(L,x)
-#endif
-
-/* PyIntBinop.proto */
-#if !CYTHON_COMPILING_IN_PYPY
-static PyObject* __Pyx_PyInt_AddObjC(PyObject *op1, PyObject *op2, long intval, int inplace, int zerodivision_check);
-#else
-#define __Pyx_PyInt_AddObjC(op1, op2, intval, inplace, zerodivision_check)\
- (inplace ? PyNumber_InPlaceAdd(op1, op2) : PyNumber_Add(op1, op2))
-#endif
-
-/* ListExtend.proto */
-static CYTHON_INLINE int __Pyx_PyList_Extend(PyObject* L, PyObject* v) {
-#if CYTHON_COMPILING_IN_CPYTHON
- PyObject* none = _PyList_Extend((PyListObject*)L, v);
- if (unlikely(!none))
- return -1;
- Py_DECREF(none);
- return 0;
-#else
- return PyList_SetSlice(L, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, v);
-#endif
-}
-
-/* ListAppend.proto */
-#if CYTHON_USE_PYLIST_INTERNALS && CYTHON_ASSUME_SAFE_MACROS
-static CYTHON_INLINE int __Pyx_PyList_Append(PyObject* list, PyObject* x) {
- PyListObject* L = (PyListObject*) list;
- Py_ssize_t len = Py_SIZE(list);
- if (likely(L->allocated > len) & likely(len > (L->allocated >> 1))) {
- Py_INCREF(x);
- PyList_SET_ITEM(list, len, x);
- Py_SIZE(list) = len+1;
- return 0;
- }
- return PyList_Append(list, x);
-}
-#else
-#define __Pyx_PyList_Append(L,x) PyList_Append(L,x)
-#endif
-
-/* None.proto */
-static CYTHON_INLINE void __Pyx_RaiseUnboundLocalError(const char *varname);
-
-/* None.proto */
-static CYTHON_INLINE long __Pyx_div_long(long, long);
-
-/* ImportFrom.proto */
-static PyObject* __Pyx_ImportFrom(PyObject* module, PyObject* name);
-
-/* HasAttr.proto */
-static CYTHON_INLINE int __Pyx_HasAttr(PyObject *, PyObject *);
-
-/* PyObject_GenericGetAttrNoDict.proto */
-#if CYTHON_USE_TYPE_SLOTS && CYTHON_USE_PYTYPE_LOOKUP && PY_VERSION_HEX < 0x03070000
-static CYTHON_INLINE PyObject* __Pyx_PyObject_GenericGetAttrNoDict(PyObject* obj, PyObject* attr_name);
-#else
-#define __Pyx_PyObject_GenericGetAttrNoDict PyObject_GenericGetAttr
-#endif
-
-/* PyObject_GenericGetAttr.proto */
-#if CYTHON_USE_TYPE_SLOTS && CYTHON_USE_PYTYPE_LOOKUP && PY_VERSION_HEX < 0x03070000
-static PyObject* __Pyx_PyObject_GenericGetAttr(PyObject* obj, PyObject* attr_name);
-#else
-#define __Pyx_PyObject_GenericGetAttr PyObject_GenericGetAttr
-#endif
-
-/* SetVTable.proto */
-static int __Pyx_SetVtable(PyObject *dict, void *vtable);
-
-/* SetupReduce.proto */
-static int __Pyx_setup_reduce(PyObject* type_obj);
-
-/* CLineInTraceback.proto */
-#ifdef CYTHON_CLINE_IN_TRACEBACK
-#define __Pyx_CLineForTraceback(tstate, c_line) (((CYTHON_CLINE_IN_TRACEBACK)) ? c_line : 0)
-#else
-static int __Pyx_CLineForTraceback(PyThreadState *tstate, int c_line);
-#endif
-
-/* CodeObjectCache.proto */
-typedef struct {
- PyCodeObject* code_object;
- int code_line;
-} __Pyx_CodeObjectCacheEntry;
-struct __Pyx_CodeObjectCache {
- int count;
- int max_count;
- __Pyx_CodeObjectCacheEntry* entries;
-};
-static struct __Pyx_CodeObjectCache __pyx_code_cache = {0,0,NULL};
-static int __pyx_bisect_code_objects(__Pyx_CodeObjectCacheEntry* entries, int count, int code_line);
-static PyCodeObject *__pyx_find_code_object(int code_line);
-static void __pyx_insert_code_object(int code_line, PyCodeObject* code_object);
-
-/* AddTraceback.proto */
-static void __Pyx_AddTraceback(const char *funcname, int c_line,
- int py_line, const char *filename);
-
-#if PY_MAJOR_VERSION < 3
- static int __Pyx_GetBuffer(PyObject *obj, Py_buffer *view, int flags);
- static void __Pyx_ReleaseBuffer(Py_buffer *view);
-#else
- #define __Pyx_GetBuffer PyObject_GetBuffer
- #define __Pyx_ReleaseBuffer PyBuffer_Release
-#endif
-
-
-/* BufferStructDeclare.proto */
-typedef struct {
- Py_ssize_t shape, strides, suboffsets;
-} __Pyx_Buf_DimInfo;
-typedef struct {
- size_t refcount;
- Py_buffer pybuffer;
-} __Pyx_Buffer;
-typedef struct {
- __Pyx_Buffer *rcbuffer;
- char *data;
- __Pyx_Buf_DimInfo diminfo[8];
-} __Pyx_LocalBuf_ND;
-
-/* MemviewSliceIsContig.proto */
-static int __pyx_memviewslice_is_contig(const __Pyx_memviewslice mvs, char order, int ndim);
-
-/* OverlappingSlices.proto */
-static int __pyx_slices_overlap(__Pyx_memviewslice *slice1,
- __Pyx_memviewslice *slice2,
- int ndim, size_t itemsize);
-
-/* Capsule.proto */
-static CYTHON_INLINE PyObject *__pyx_capsule_create(void *p, const char *sig);
-
-/* CIntToPy.proto */
-static CYTHON_INLINE PyObject* __Pyx_PyInt_From_long(long value);
-
-/* CIntToPy.proto */
-static CYTHON_INLINE PyObject* __Pyx_PyInt_From_int(int value);
-
-/* MemviewDtypeToObject.proto */
-static CYTHON_INLINE PyObject *__pyx_memview_get_float(const char *itemp);
-static CYTHON_INLINE int __pyx_memview_set_float(const char *itemp, PyObject *obj);
-
-/* MemviewSliceCopyTemplate.proto */
-static __Pyx_memviewslice
-__pyx_memoryview_copy_new_contig(const __Pyx_memviewslice *from_mvs,
- const char *mode, int ndim,
- size_t sizeof_dtype, int contig_flag,
- int dtype_is_object);
-
-/* CIntFromPy.proto */
-static CYTHON_INLINE size_t __Pyx_PyInt_As_size_t(PyObject *);
-
-/* CIntFromPy.proto */
-static CYTHON_INLINE int __Pyx_PyInt_As_int(PyObject *);
-
-/* CIntFromPy.proto */
-static CYTHON_INLINE long __Pyx_PyInt_As_long(PyObject *);
-
-/* CIntFromPy.proto */
-static CYTHON_INLINE char __Pyx_PyInt_As_char(PyObject *);
-
-/* IsLittleEndian.proto */
-static CYTHON_INLINE int __Pyx_Is_Little_Endian(void);
-
-/* BufferFormatCheck.proto */
-static const char* __Pyx_BufFmt_CheckString(__Pyx_BufFmt_Context* ctx, const char* ts);
-static void __Pyx_BufFmt_Init(__Pyx_BufFmt_Context* ctx,
- __Pyx_BufFmt_StackElem* stack,
- __Pyx_TypeInfo* type);
-
-/* TypeInfoCompare.proto */
-static int __pyx_typeinfo_cmp(__Pyx_TypeInfo *a, __Pyx_TypeInfo *b);
-
-/* MemviewSliceValidateAndInit.proto */
-static int __Pyx_ValidateAndInit_memviewslice(
- int *axes_specs,
- int c_or_f_flag,
- int buf_flags,
- int ndim,
- __Pyx_TypeInfo *dtype,
- __Pyx_BufFmt_StackElem stack[],
- __Pyx_memviewslice *memviewslice,
- PyObject *original_obj);
-
-/* ObjectToMemviewSlice.proto */
-static CYTHON_INLINE __Pyx_memviewslice __Pyx_PyObject_to_MemoryviewSlice_d_dc_float(PyObject *, int writable_flag);
-
-/* ObjectToMemviewSlice.proto */
-static CYTHON_INLINE __Pyx_memviewslice __Pyx_PyObject_to_MemoryviewSlice_ds_float(PyObject *, int writable_flag);
-
-/* ObjectToMemviewSlice.proto */
-static CYTHON_INLINE __Pyx_memviewslice __Pyx_PyObject_to_MemoryviewSlice_dc_float(PyObject *, int writable_flag);
-
-/* CheckBinaryVersion.proto */
-static int __Pyx_check_binary_version(void);
-
-/* InitStrings.proto */
-static int __Pyx_InitStrings(__Pyx_StringTabEntry *t);
-
-static float __pyx_f_4silx_5image_8bilinear_13BilinearImage_c_funct(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self, float __pyx_v_x, float __pyx_v_y); /* proto*/
-static size_t __pyx_f_4silx_5image_8bilinear_13BilinearImage_coarse_local_maxi(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self, size_t __pyx_v_x, int __pyx_skip_dispatch); /* proto*/
-static size_t __pyx_f_4silx_5image_8bilinear_13BilinearImage_c_local_maxi(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self, size_t __pyx_v_idx); /* proto*/
-static PyObject *__pyx_array_get_memview(struct __pyx_array_obj *__pyx_v_self); /* proto*/
-static char *__pyx_memoryview_get_item_pointer(struct __pyx_memoryview_obj *__pyx_v_self, PyObject *__pyx_v_index); /* proto*/
-static PyObject *__pyx_memoryview_is_slice(struct __pyx_memoryview_obj *__pyx_v_self, PyObject *__pyx_v_obj); /* proto*/
-static PyObject *__pyx_memoryview_setitem_slice_assignment(struct __pyx_memoryview_obj *__pyx_v_self, PyObject *__pyx_v_dst, PyObject *__pyx_v_src); /* proto*/
-static PyObject *__pyx_memoryview_setitem_slice_assign_scalar(struct __pyx_memoryview_obj *__pyx_v_self, struct __pyx_memoryview_obj *__pyx_v_dst, PyObject *__pyx_v_value); /* proto*/
-static PyObject *__pyx_memoryview_setitem_indexed(struct __pyx_memoryview_obj *__pyx_v_self, PyObject *__pyx_v_index, PyObject *__pyx_v_value); /* proto*/
-static PyObject *__pyx_memoryview_convert_item_to_object(struct __pyx_memoryview_obj *__pyx_v_self, char *__pyx_v_itemp); /* proto*/
-static PyObject *__pyx_memoryview_assign_item_from_object(struct __pyx_memoryview_obj *__pyx_v_self, char *__pyx_v_itemp, PyObject *__pyx_v_value); /* proto*/
-static PyObject *__pyx_memoryviewslice_convert_item_to_object(struct __pyx_memoryviewslice_obj *__pyx_v_self, char *__pyx_v_itemp); /* proto*/
-static PyObject *__pyx_memoryviewslice_assign_item_from_object(struct __pyx_memoryviewslice_obj *__pyx_v_self, char *__pyx_v_itemp, PyObject *__pyx_v_value); /* proto*/
-
-/* Module declarations from 'cython.view' */
-
-/* Module declarations from 'cython' */
-
-/* Module declarations from 'libc.math' */
-
-/* Module declarations from 'silx.image.bilinear' */
-static PyTypeObject *__pyx_ptype_4silx_5image_8bilinear_BilinearImage = 0;
-static PyTypeObject *__pyx_array_type = 0;
-static PyTypeObject *__pyx_MemviewEnum_type = 0;
-static PyTypeObject *__pyx_memoryview_type = 0;
-static PyTypeObject *__pyx_memoryviewslice_type = 0;
-static PyObject *generic = 0;
-static PyObject *strided = 0;
-static PyObject *indirect = 0;
-static PyObject *contiguous = 0;
-static PyObject *indirect_contiguous = 0;
-static int __pyx_memoryview_thread_locks_used;
-static PyThread_type_lock __pyx_memoryview_thread_locks[8];
-static struct __pyx_array_obj *__pyx_array_new(PyObject *, Py_ssize_t, char *, char *, char *); /*proto*/
-static void *__pyx_align_pointer(void *, size_t); /*proto*/
-static PyObject *__pyx_memoryview_new(PyObject *, int, int, __Pyx_TypeInfo *); /*proto*/
-static CYTHON_INLINE int __pyx_memoryview_check(PyObject *); /*proto*/
-static PyObject *_unellipsify(PyObject *, int); /*proto*/
-static PyObject *assert_direct_dimensions(Py_ssize_t *, int); /*proto*/
-static struct __pyx_memoryview_obj *__pyx_memview_slice(struct __pyx_memoryview_obj *, PyObject *); /*proto*/
-static int __pyx_memoryview_slice_memviewslice(__Pyx_memviewslice *, Py_ssize_t, Py_ssize_t, Py_ssize_t, int, int, int *, Py_ssize_t, Py_ssize_t, Py_ssize_t, int, int, int, int); /*proto*/
-static char *__pyx_pybuffer_index(Py_buffer *, char *, Py_ssize_t, Py_ssize_t); /*proto*/
-static int __pyx_memslice_transpose(__Pyx_memviewslice *); /*proto*/
-static PyObject *__pyx_memoryview_fromslice(__Pyx_memviewslice, int, PyObject *(*)(char *), int (*)(char *, PyObject *), int); /*proto*/
-static __Pyx_memviewslice *__pyx_memoryview_get_slice_from_memoryview(struct __pyx_memoryview_obj *, __Pyx_memviewslice *); /*proto*/
-static void __pyx_memoryview_slice_copy(struct __pyx_memoryview_obj *, __Pyx_memviewslice *); /*proto*/
-static PyObject *__pyx_memoryview_copy_object(struct __pyx_memoryview_obj *); /*proto*/
-static PyObject *__pyx_memoryview_copy_object_from_slice(struct __pyx_memoryview_obj *, __Pyx_memviewslice *); /*proto*/
-static Py_ssize_t abs_py_ssize_t(Py_ssize_t); /*proto*/
-static char __pyx_get_best_slice_order(__Pyx_memviewslice *, int); /*proto*/
-static void _copy_strided_to_strided(char *, Py_ssize_t *, char *, Py_ssize_t *, Py_ssize_t *, Py_ssize_t *, int, size_t); /*proto*/
-static void copy_strided_to_strided(__Pyx_memviewslice *, __Pyx_memviewslice *, int, size_t); /*proto*/
-static Py_ssize_t __pyx_memoryview_slice_get_size(__Pyx_memviewslice *, int); /*proto*/
-static Py_ssize_t __pyx_fill_contig_strides_array(Py_ssize_t *, Py_ssize_t *, Py_ssize_t, int, char); /*proto*/
-static void *__pyx_memoryview_copy_data_to_temp(__Pyx_memviewslice *, __Pyx_memviewslice *, char, int); /*proto*/
-static int __pyx_memoryview_err_extents(int, Py_ssize_t, Py_ssize_t); /*proto*/
-static int __pyx_memoryview_err_dim(PyObject *, char *, int); /*proto*/
-static int __pyx_memoryview_err(PyObject *, char *); /*proto*/
-static int __pyx_memoryview_copy_contents(__Pyx_memviewslice, __Pyx_memviewslice, int, int, int); /*proto*/
-static void __pyx_memoryview_broadcast_leading(__Pyx_memviewslice *, int, int); /*proto*/
-static void __pyx_memoryview_refcount_copying(__Pyx_memviewslice *, int, int, int); /*proto*/
-static void __pyx_memoryview_refcount_objects_in_slice_with_gil(char *, Py_ssize_t *, Py_ssize_t *, int, int); /*proto*/
-static void __pyx_memoryview_refcount_objects_in_slice(char *, Py_ssize_t *, Py_ssize_t *, int, int); /*proto*/
-static void __pyx_memoryview_slice_assign_scalar(__Pyx_memviewslice *, int, size_t, void *, int); /*proto*/
-static void __pyx_memoryview__slice_assign_scalar(char *, Py_ssize_t *, Py_ssize_t *, int, size_t, void *); /*proto*/
-static PyObject *__pyx_unpickle_Enum__set_state(struct __pyx_MemviewEnum_obj *, PyObject *); /*proto*/
-static __Pyx_TypeInfo __Pyx_TypeInfo_float = { "float", NULL, sizeof(float), { 0 }, 0, 'R', 0, 0 };
-#define __Pyx_MODULE_NAME "silx.image.bilinear"
-extern int __pyx_module_is_main_silx__image__bilinear;
-int __pyx_module_is_main_silx__image__bilinear = 0;
-
-/* Implementation of 'silx.image.bilinear' */
-static PyObject *__pyx_builtin_round;
-static PyObject *__pyx_builtin_range;
-static PyObject *__pyx_builtin_TypeError;
-static PyObject *__pyx_builtin_ValueError;
-static PyObject *__pyx_builtin_MemoryError;
-static PyObject *__pyx_builtin_enumerate;
-static PyObject *__pyx_builtin_Ellipsis;
-static PyObject *__pyx_builtin_id;
-static PyObject *__pyx_builtin_IndexError;
-static const char __pyx_k_O[] = "O";
-static const char __pyx_k_c[] = "c";
-static const char __pyx_k_id[] = "id";
-static const char __pyx_k_MIT[] = "MIT";
-static const char __pyx_k_doc[] = "__doc__";
-static const char __pyx_k_dst[] = "dst";
-static const char __pyx_k_max[] = "max";
-static const char __pyx_k_min[] = "min";
-static const char __pyx_k_new[] = "__new__";
-static const char __pyx_k_obj[] = "obj";
-static const char __pyx_k_src[] = "src";
-static const char __pyx_k_base[] = "base";
-static const char __pyx_k_data[] = "data";
-static const char __pyx_k_date[] = "__date__";
-static const char __pyx_k_dict[] = "__dict__";
-static const char __pyx_k_main[] = "__main__";
-static const char __pyx_k_mean[] = "mean";
-static const char __pyx_k_mode[] = "mode";
-static const char __pyx_k_name[] = "name";
-static const char __pyx_k_ndim[] = "ndim";
-static const char __pyx_k_pack[] = "pack";
-static const char __pyx_k_size[] = "size";
-static const char __pyx_k_step[] = "step";
-static const char __pyx_k_stop[] = "stop";
-static const char __pyx_k_test[] = "__test__";
-static const char __pyx_k_ASCII[] = "ASCII";
-static const char __pyx_k_array[] = "array";
-static const char __pyx_k_class[] = "__class__";
-static const char __pyx_k_coord[] = "coord";
-static const char __pyx_k_debug[] = "debug";
-static const char __pyx_k_dtype[] = "dtype";
-static const char __pyx_k_empty[] = "empty";
-static const char __pyx_k_error[] = "error";
-static const char __pyx_k_flags[] = "flags";
-static const char __pyx_k_numpy[] = "numpy";
-static const char __pyx_k_range[] = "range";
-static const char __pyx_k_ravel[] = "ravel";
-static const char __pyx_k_round[] = "round";
-static const char __pyx_k_shape[] = "shape";
-static const char __pyx_k_start[] = "start";
-static const char __pyx_k_zeros[] = "zeros";
-static const char __pyx_k_encode[] = "encode";
-static const char __pyx_k_format[] = "format";
-static const char __pyx_k_import[] = "__import__";
-static const char __pyx_k_logger[] = "logger";
-static const char __pyx_k_method[] = "method";
-static const char __pyx_k_name_2[] = "__name__";
-static const char __pyx_k_pickle[] = "pickle";
-static const char __pyx_k_reduce[] = "__reduce__";
-static const char __pyx_k_struct[] = "struct";
-static const char __pyx_k_unpack[] = "unpack";
-static const char __pyx_k_update[] = "update";
-static const char __pyx_k_asarray[] = "asarray";
-static const char __pyx_k_authors[] = "__authors__";
-static const char __pyx_k_float32[] = "float32";
-static const char __pyx_k_fortran[] = "fortran";
-static const char __pyx_k_license[] = "__license__";
-static const char __pyx_k_logging[] = "logging";
-static const char __pyx_k_memview[] = "memview";
-static const char __pyx_k_reshape[] = "reshape";
-static const char __pyx_k_warning[] = "warning";
-static const char __pyx_k_Ellipsis[] = "Ellipsis";
-static const char __pyx_k_getstate[] = "__getstate__";
-static const char __pyx_k_itemsize[] = "itemsize";
-static const char __pyx_k_pyx_type[] = "__pyx_type";
-static const char __pyx_k_setstate[] = "__setstate__";
-static const char __pyx_k_J_Kieffer[] = "J. Kieffer";
-static const char __pyx_k_TypeError[] = "TypeError";
-static const char __pyx_k_enumerate[] = "enumerate";
-static const char __pyx_k_getLogger[] = "getLogger";
-static const char __pyx_k_linewidth[] = "linewidth";
-static const char __pyx_k_pyx_state[] = "__pyx_state";
-static const char __pyx_k_reduce_ex[] = "__reduce_ex__";
-static const char __pyx_k_15_09_2016[] = "15/09/2016";
-static const char __pyx_k_IndexError[] = "IndexError";
-static const char __pyx_k_ValueError[] = "ValueError";
-static const char __pyx_k_pyx_result[] = "__pyx_result";
-static const char __pyx_k_pyx_vtable[] = "__pyx_vtable__";
-static const char __pyx_k_MemoryError[] = "MemoryError";
-static const char __pyx_k_PickleError[] = "PickleError";
-static const char __pyx_k_pyx_checksum[] = "__pyx_checksum";
-static const char __pyx_k_stringsource[] = "stringsource";
-static const char __pyx_k_BilinearImage[] = "BilinearImage";
-static const char __pyx_k_pyx_getbuffer[] = "__pyx_getbuffer";
-static const char __pyx_k_reduce_cython[] = "__reduce_cython__";
-static const char __pyx_k_View_MemoryView[] = "View.MemoryView";
-static const char __pyx_k_allocate_buffer[] = "allocate_buffer";
-static const char __pyx_k_dtype_is_object[] = "dtype_is_object";
-static const char __pyx_k_pyx_PickleError[] = "__pyx_PickleError";
-static const char __pyx_k_setstate_cython[] = "__setstate_cython__";
-static const char __pyx_k_ascontiguousarray[] = "ascontiguousarray";
-static const char __pyx_k_coarse_local_maxi[] = "coarse_local_maxi";
-static const char __pyx_k_pyx_unpickle_Enum[] = "__pyx_unpickle_Enum";
-static const char __pyx_k_cline_in_traceback[] = "cline_in_traceback";
-static const char __pyx_k_strided_and_direct[] = "<strided and direct>";
-static const char __pyx_k_strided_and_indirect[] = "<strided and indirect>";
-static const char __pyx_k_contiguous_and_direct[] = "<contiguous and direct>";
-static const char __pyx_k_MemoryView_of_r_object[] = "<MemoryView of %r object>";
-static const char __pyx_k_MemoryView_of_r_at_0x_x[] = "<MemoryView of %r at 0x%x>";
-static const char __pyx_k_contiguous_and_indirect[] = "<contiguous and indirect>";
-static const char __pyx_k_Cannot_index_with_type_s[] = "Cannot index with type '%s'";
-static const char __pyx_k_Invalid_shape_in_axis_d_d[] = "Invalid shape in axis %d: %d.";
-static const char __pyx_k_itemsize_0_for_cython_array[] = "itemsize <= 0 for cython.array";
-static const char __pyx_k_unable_to_allocate_array_data[] = "unable to allocate array data.";
-static const char __pyx_k_strided_and_direct_or_indirect[] = "<strided and direct or indirect>";
-static const char __pyx_k_Bilinear_interpolator_peak_finde[] = "Bilinear interpolator, peak finder, line-profile for images";
-static const char __pyx_k_Buffer_view_does_not_expose_stri[] = "Buffer view does not expose strides";
-static const char __pyx_k_Can_only_create_a_buffer_that_is[] = "Can only create a buffer that is contiguous in memory.";
-static const char __pyx_k_Cannot_assign_to_read_only_memor[] = "Cannot assign to read-only memoryview";
-static const char __pyx_k_Cannot_create_writable_memory_vi[] = "Cannot create writable memory view from read-only memoryview";
-static const char __pyx_k_Empty_shape_tuple_for_cython_arr[] = "Empty shape tuple for cython.array";
-static const char __pyx_k_Failed_to_find_root_using_second[] = "Failed to find root using second order expansion";
-static const char __pyx_k_Incompatible_checksums_s_vs_0xb0[] = "Incompatible checksums (%s vs 0xb068931 = (name))";
-static const char __pyx_k_Indirect_dimensions_not_supporte[] = "Indirect dimensions not supported";
-static const char __pyx_k_Invalid_mode_expected_c_or_fortr[] = "Invalid mode, expected 'c' or 'fortran', got %s";
-static const char __pyx_k_Out_of_bounds_on_buffer_access_a[] = "Out of bounds on buffer access (axis %d)";
-static const char __pyx_k_Singular_determinant_Hessian_und[] = "Singular determinant, Hessian undefined";
-static const char __pyx_k_Source_and_destination_points_ar[] = "Source and destination points are the same";
-static const char __pyx_k_Unable_to_convert_item_to_object[] = "Unable to convert item to object";
-static const char __pyx_k_got_differing_extents_in_dimensi[] = "got differing extents in dimension %d (got %d and %d)";
-static const char __pyx_k_no_default___reduce___due_to_non[] = "no default __reduce__ due to non-trivial __cinit__";
-static const char __pyx_k_unable_to_allocate_shape_and_str[] = "unable to allocate shape and strides.";
-static PyObject *__pyx_kp_u_15_09_2016;
-static PyObject *__pyx_n_s_ASCII;
-static PyObject *__pyx_n_s_BilinearImage;
-static PyObject *__pyx_kp_u_Bilinear_interpolator_peak_finde;
-static PyObject *__pyx_kp_s_Buffer_view_does_not_expose_stri;
-static PyObject *__pyx_kp_s_Can_only_create_a_buffer_that_is;
-static PyObject *__pyx_kp_s_Cannot_assign_to_read_only_memor;
-static PyObject *__pyx_kp_s_Cannot_create_writable_memory_vi;
-static PyObject *__pyx_kp_s_Cannot_index_with_type_s;
-static PyObject *__pyx_n_s_Ellipsis;
-static PyObject *__pyx_kp_s_Empty_shape_tuple_for_cython_arr;
-static PyObject *__pyx_kp_u_Failed_to_find_root_using_second;
-static PyObject *__pyx_kp_s_Incompatible_checksums_s_vs_0xb0;
-static PyObject *__pyx_n_s_IndexError;
-static PyObject *__pyx_kp_s_Indirect_dimensions_not_supporte;
-static PyObject *__pyx_kp_s_Invalid_mode_expected_c_or_fortr;
-static PyObject *__pyx_kp_s_Invalid_shape_in_axis_d_d;
-static PyObject *__pyx_kp_u_J_Kieffer;
-static PyObject *__pyx_n_u_MIT;
-static PyObject *__pyx_n_s_MemoryError;
-static PyObject *__pyx_kp_s_MemoryView_of_r_at_0x_x;
-static PyObject *__pyx_kp_s_MemoryView_of_r_object;
-static PyObject *__pyx_n_b_O;
-static PyObject *__pyx_kp_s_Out_of_bounds_on_buffer_access_a;
-static PyObject *__pyx_n_s_PickleError;
-static PyObject *__pyx_kp_u_Singular_determinant_Hessian_und;
-static PyObject *__pyx_kp_u_Source_and_destination_points_ar;
-static PyObject *__pyx_n_s_TypeError;
-static PyObject *__pyx_kp_s_Unable_to_convert_item_to_object;
-static PyObject *__pyx_n_s_ValueError;
-static PyObject *__pyx_n_s_View_MemoryView;
-static PyObject *__pyx_n_s_allocate_buffer;
-static PyObject *__pyx_n_s_array;
-static PyObject *__pyx_n_s_asarray;
-static PyObject *__pyx_n_s_ascontiguousarray;
-static PyObject *__pyx_n_s_authors;
-static PyObject *__pyx_n_s_base;
-static PyObject *__pyx_n_s_c;
-static PyObject *__pyx_n_u_c;
-static PyObject *__pyx_n_s_class;
-static PyObject *__pyx_n_s_cline_in_traceback;
-static PyObject *__pyx_n_s_coarse_local_maxi;
-static PyObject *__pyx_kp_s_contiguous_and_direct;
-static PyObject *__pyx_kp_s_contiguous_and_indirect;
-static PyObject *__pyx_n_s_coord;
-static PyObject *__pyx_n_s_data;
-static PyObject *__pyx_n_s_date;
-static PyObject *__pyx_n_s_debug;
-static PyObject *__pyx_n_s_dict;
-static PyObject *__pyx_n_s_doc;
-static PyObject *__pyx_n_s_dst;
-static PyObject *__pyx_n_s_dtype;
-static PyObject *__pyx_n_s_dtype_is_object;
-static PyObject *__pyx_n_s_empty;
-static PyObject *__pyx_n_s_encode;
-static PyObject *__pyx_n_s_enumerate;
-static PyObject *__pyx_n_s_error;
-static PyObject *__pyx_n_s_flags;
-static PyObject *__pyx_n_s_float32;
-static PyObject *__pyx_n_s_format;
-static PyObject *__pyx_n_s_fortran;
-static PyObject *__pyx_n_u_fortran;
-static PyObject *__pyx_n_s_getLogger;
-static PyObject *__pyx_n_s_getstate;
-static PyObject *__pyx_kp_s_got_differing_extents_in_dimensi;
-static PyObject *__pyx_n_s_id;
-static PyObject *__pyx_n_s_import;
-static PyObject *__pyx_n_s_itemsize;
-static PyObject *__pyx_kp_s_itemsize_0_for_cython_array;
-static PyObject *__pyx_n_s_license;
-static PyObject *__pyx_n_s_linewidth;
-static PyObject *__pyx_n_s_logger;
-static PyObject *__pyx_n_s_logging;
-static PyObject *__pyx_n_s_main;
-static PyObject *__pyx_n_s_max;
-static PyObject *__pyx_n_u_mean;
-static PyObject *__pyx_n_s_memview;
-static PyObject *__pyx_n_s_method;
-static PyObject *__pyx_n_s_min;
-static PyObject *__pyx_n_s_mode;
-static PyObject *__pyx_n_s_name;
-static PyObject *__pyx_n_s_name_2;
-static PyObject *__pyx_n_s_ndim;
-static PyObject *__pyx_n_s_new;
-static PyObject *__pyx_kp_s_no_default___reduce___due_to_non;
-static PyObject *__pyx_n_s_numpy;
-static PyObject *__pyx_n_s_obj;
-static PyObject *__pyx_n_s_pack;
-static PyObject *__pyx_n_s_pickle;
-static PyObject *__pyx_n_s_pyx_PickleError;
-static PyObject *__pyx_n_s_pyx_checksum;
-static PyObject *__pyx_n_s_pyx_getbuffer;
-static PyObject *__pyx_n_s_pyx_result;
-static PyObject *__pyx_n_s_pyx_state;
-static PyObject *__pyx_n_s_pyx_type;
-static PyObject *__pyx_n_s_pyx_unpickle_Enum;
-static PyObject *__pyx_n_s_pyx_vtable;
-static PyObject *__pyx_n_s_range;
-static PyObject *__pyx_n_s_ravel;
-static PyObject *__pyx_n_s_reduce;
-static PyObject *__pyx_n_s_reduce_cython;
-static PyObject *__pyx_n_s_reduce_ex;
-static PyObject *__pyx_n_s_reshape;
-static PyObject *__pyx_n_s_round;
-static PyObject *__pyx_n_s_setstate;
-static PyObject *__pyx_n_s_setstate_cython;
-static PyObject *__pyx_n_s_shape;
-static PyObject *__pyx_n_s_size;
-static PyObject *__pyx_n_s_src;
-static PyObject *__pyx_n_s_start;
-static PyObject *__pyx_n_s_step;
-static PyObject *__pyx_n_s_stop;
-static PyObject *__pyx_kp_s_strided_and_direct;
-static PyObject *__pyx_kp_s_strided_and_direct_or_indirect;
-static PyObject *__pyx_kp_s_strided_and_indirect;
-static PyObject *__pyx_kp_s_stringsource;
-static PyObject *__pyx_n_s_struct;
-static PyObject *__pyx_n_s_test;
-static PyObject *__pyx_kp_s_unable_to_allocate_array_data;
-static PyObject *__pyx_kp_s_unable_to_allocate_shape_and_str;
-static PyObject *__pyx_n_s_unpack;
-static PyObject *__pyx_n_s_update;
-static PyObject *__pyx_n_s_warning;
-static PyObject *__pyx_n_s_zeros;
-static int __pyx_pf_4silx_5image_8bilinear_13BilinearImage___cinit__(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self, PyObject *__pyx_v_data); /* proto */
-static void __pyx_pf_4silx_5image_8bilinear_13BilinearImage_2__dealloc__(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_4silx_5image_8bilinear_13BilinearImage_4__call__(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self, PyObject *__pyx_v_coord); /* proto */
-static PyObject *__pyx_pf_4silx_5image_8bilinear_13BilinearImage_6opp_f(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self, PyObject *__pyx_v_coord); /* proto */
-static PyObject *__pyx_pf_4silx_5image_8bilinear_13BilinearImage_8local_maxi(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self, PyObject *__pyx_v_coord); /* proto */
-static PyObject *__pyx_pf_4silx_5image_8bilinear_13BilinearImage_10coarse_local_maxi(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self, size_t __pyx_v_x); /* proto */
-static PyObject *__pyx_pf_4silx_5image_8bilinear_13BilinearImage_12map_coordinates(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self, PyObject *__pyx_v_coordinates); /* proto */
-static PyObject *__pyx_pf_4silx_5image_8bilinear_13BilinearImage_14profile_line(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self, PyObject *__pyx_v_src, PyObject *__pyx_v_dst, int __pyx_v_linewidth, PyObject *__pyx_v_method); /* proto */
-static PyObject *__pyx_pf_4silx_5image_8bilinear_13BilinearImage_4data___get__(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_4silx_5image_8bilinear_13BilinearImage_4maxi___get__(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_4silx_5image_8bilinear_13BilinearImage_4mini___get__(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_4silx_5image_8bilinear_13BilinearImage_5width___get__(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_4silx_5image_8bilinear_13BilinearImage_6height___get__(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_4silx_5image_8bilinear_13BilinearImage_16__reduce_cython__(CYTHON_UNUSED struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_4silx_5image_8bilinear_13BilinearImage_18__setstate_cython__(CYTHON_UNUSED struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self, CYTHON_UNUSED PyObject *__pyx_v___pyx_state); /* proto */
-static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array___cinit__(struct __pyx_array_obj *__pyx_v_self, PyObject *__pyx_v_shape, Py_ssize_t __pyx_v_itemsize, PyObject *__pyx_v_format, PyObject *__pyx_v_mode, int __pyx_v_allocate_buffer); /* proto */
-static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array_2__getbuffer__(struct __pyx_array_obj *__pyx_v_self, Py_buffer *__pyx_v_info, int __pyx_v_flags); /* proto */
-static void __pyx_array___pyx_pf_15View_dot_MemoryView_5array_4__dealloc__(struct __pyx_array_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_15View_dot_MemoryView_5array_7memview___get__(struct __pyx_array_obj *__pyx_v_self); /* proto */
-static Py_ssize_t __pyx_array___pyx_pf_15View_dot_MemoryView_5array_6__len__(struct __pyx_array_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_array___pyx_pf_15View_dot_MemoryView_5array_8__getattr__(struct __pyx_array_obj *__pyx_v_self, PyObject *__pyx_v_attr); /* proto */
-static PyObject *__pyx_array___pyx_pf_15View_dot_MemoryView_5array_10__getitem__(struct __pyx_array_obj *__pyx_v_self, PyObject *__pyx_v_item); /* proto */
-static int __pyx_array___pyx_pf_15View_dot_MemoryView_5array_12__setitem__(struct __pyx_array_obj *__pyx_v_self, PyObject *__pyx_v_item, PyObject *__pyx_v_value); /* proto */
-static PyObject *__pyx_pf___pyx_array___reduce_cython__(CYTHON_UNUSED struct __pyx_array_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf___pyx_array_2__setstate_cython__(CYTHON_UNUSED struct __pyx_array_obj *__pyx_v_self, CYTHON_UNUSED PyObject *__pyx_v___pyx_state); /* proto */
-static int __pyx_MemviewEnum___pyx_pf_15View_dot_MemoryView_4Enum___init__(struct __pyx_MemviewEnum_obj *__pyx_v_self, PyObject *__pyx_v_name); /* proto */
-static PyObject *__pyx_MemviewEnum___pyx_pf_15View_dot_MemoryView_4Enum_2__repr__(struct __pyx_MemviewEnum_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf___pyx_MemviewEnum___reduce_cython__(struct __pyx_MemviewEnum_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf___pyx_MemviewEnum_2__setstate_cython__(struct __pyx_MemviewEnum_obj *__pyx_v_self, PyObject *__pyx_v___pyx_state); /* proto */
-static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview___cinit__(struct __pyx_memoryview_obj *__pyx_v_self, PyObject *__pyx_v_obj, int __pyx_v_flags, int __pyx_v_dtype_is_object); /* proto */
-static void __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_2__dealloc__(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_4__getitem__(struct __pyx_memoryview_obj *__pyx_v_self, PyObject *__pyx_v_index); /* proto */
-static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_6__setitem__(struct __pyx_memoryview_obj *__pyx_v_self, PyObject *__pyx_v_index, PyObject *__pyx_v_value); /* proto */
-static int __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_8__getbuffer__(struct __pyx_memoryview_obj *__pyx_v_self, Py_buffer *__pyx_v_info, int __pyx_v_flags); /* proto */
-static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_1T___get__(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4base___get__(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_5shape___get__(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_7strides___get__(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_10suboffsets___get__(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4ndim___get__(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_8itemsize___get__(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_6nbytes___get__(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_15View_dot_MemoryView_10memoryview_4size___get__(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static Py_ssize_t __pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_10__len__(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_12__repr__(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_14__str__(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_16is_c_contig(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_18is_f_contig(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_20copy(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_memoryview___pyx_pf_15View_dot_MemoryView_10memoryview_22copy_fortran(struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf___pyx_memoryview___reduce_cython__(CYTHON_UNUSED struct __pyx_memoryview_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf___pyx_memoryview_2__setstate_cython__(CYTHON_UNUSED struct __pyx_memoryview_obj *__pyx_v_self, CYTHON_UNUSED PyObject *__pyx_v___pyx_state); /* proto */
-static void __pyx_memoryviewslice___pyx_pf_15View_dot_MemoryView_16_memoryviewslice___dealloc__(struct __pyx_memoryviewslice_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf_15View_dot_MemoryView_16_memoryviewslice_4base___get__(struct __pyx_memoryviewslice_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf___pyx_memoryviewslice___reduce_cython__(CYTHON_UNUSED struct __pyx_memoryviewslice_obj *__pyx_v_self); /* proto */
-static PyObject *__pyx_pf___pyx_memoryviewslice_2__setstate_cython__(CYTHON_UNUSED struct __pyx_memoryviewslice_obj *__pyx_v_self, CYTHON_UNUSED PyObject *__pyx_v___pyx_state); /* proto */
-static PyObject *__pyx_pf_15View_dot_MemoryView___pyx_unpickle_Enum(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v___pyx_type, long __pyx_v___pyx_checksum, PyObject *__pyx_v___pyx_state); /* proto */
-static PyObject *__pyx_tp_new_4silx_5image_8bilinear_BilinearImage(PyTypeObject *t, PyObject *a, PyObject *k); /*proto*/
-static PyObject *__pyx_tp_new_array(PyTypeObject *t, PyObject *a, PyObject *k); /*proto*/
-static PyObject *__pyx_tp_new_Enum(PyTypeObject *t, PyObject *a, PyObject *k); /*proto*/
-static PyObject *__pyx_tp_new_memoryview(PyTypeObject *t, PyObject *a, PyObject *k); /*proto*/
-static PyObject *__pyx_tp_new__memoryviewslice(PyTypeObject *t, PyObject *a, PyObject *k); /*proto*/
-static PyObject *__pyx_int_0;
-static PyObject *__pyx_int_1;
-static PyObject *__pyx_int_2;
-static PyObject *__pyx_int_184977713;
-static PyObject *__pyx_int_neg_1;
-static PyObject *__pyx_tuple_;
-static PyObject *__pyx_tuple__2;
-static PyObject *__pyx_tuple__3;
-static PyObject *__pyx_tuple__4;
-static PyObject *__pyx_tuple__5;
-static PyObject *__pyx_tuple__6;
-static PyObject *__pyx_tuple__7;
-static PyObject *__pyx_tuple__8;
-static PyObject *__pyx_tuple__9;
-static PyObject *__pyx_slice__17;
-static PyObject *__pyx_tuple__10;
-static PyObject *__pyx_tuple__11;
-static PyObject *__pyx_tuple__12;
-static PyObject *__pyx_tuple__13;
-static PyObject *__pyx_tuple__14;
-static PyObject *__pyx_tuple__15;
-static PyObject *__pyx_tuple__16;
-static PyObject *__pyx_tuple__18;
-static PyObject *__pyx_tuple__19;
-static PyObject *__pyx_tuple__20;
-static PyObject *__pyx_tuple__21;
-static PyObject *__pyx_tuple__22;
-static PyObject *__pyx_tuple__23;
-static PyObject *__pyx_tuple__24;
-static PyObject *__pyx_tuple__25;
-static PyObject *__pyx_tuple__26;
-static PyObject *__pyx_codeobj__27;
-/* Late includes */
-
-/* "silx/image/bilinear.pyx":51
- * cdef float c_funct(self, float, float) nogil
- *
- * def __cinit__(self, data not None): # <<<<<<<<<<<<<<
- * """Constructor
- *
- */
-
-/* Python wrapper */
-static int __pyx_pw_4silx_5image_8bilinear_13BilinearImage_1__cinit__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
-static int __pyx_pw_4silx_5image_8bilinear_13BilinearImage_1__cinit__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
- PyObject *__pyx_v_data = 0;
- int __pyx_r;
- __Pyx_RefNannyDeclarations
- __Pyx_RefNannySetupContext("__cinit__ (wrapper)", 0);
- {
- static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_data,0};
- PyObject* values[1] = {0};
- if (unlikely(__pyx_kwds)) {
- Py_ssize_t kw_args;
- const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
- switch (pos_args) {
- case 1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
- CYTHON_FALLTHROUGH;
- case 0: break;
- default: goto __pyx_L5_argtuple_error;
- }
- kw_args = PyDict_Size(__pyx_kwds);
- switch (pos_args) {
- case 0:
- if (likely((values[0] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_data)) != 0)) kw_args--;
- else goto __pyx_L5_argtuple_error;
- }
- if (unlikely(kw_args > 0)) {
- if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "__cinit__") < 0)) __PYX_ERR(0, 51, __pyx_L3_error)
- }
- } else if (PyTuple_GET_SIZE(__pyx_args) != 1) {
- goto __pyx_L5_argtuple_error;
- } else {
- values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
- }
- __pyx_v_data = values[0];
- }
- goto __pyx_L4_argument_unpacking_done;
- __pyx_L5_argtuple_error:;
- __Pyx_RaiseArgtupleInvalid("__cinit__", 1, 1, 1, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 51, __pyx_L3_error)
- __pyx_L3_error:;
- __Pyx_AddTraceback("silx.image.bilinear.BilinearImage.__cinit__", __pyx_clineno, __pyx_lineno, __pyx_filename);
- __Pyx_RefNannyFinishContext();
- return -1;
- __pyx_L4_argument_unpacking_done:;
- if (unlikely(((PyObject *)__pyx_v_data) == Py_None)) {
- PyErr_Format(PyExc_TypeError, "Argument '%.200s' must not be None", "data"); __PYX_ERR(0, 51, __pyx_L1_error)
- }
- __pyx_r = __pyx_pf_4silx_5image_8bilinear_13BilinearImage___cinit__(((struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *)__pyx_v_self), __pyx_v_data);
-
- /* function exit code */
- goto __pyx_L0;
- __pyx_L1_error:;
- __pyx_r = -1;
- __pyx_L0:;
- __Pyx_RefNannyFinishContext();
- return __pyx_r;
-}
-
-static int __pyx_pf_4silx_5image_8bilinear_13BilinearImage___cinit__(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self, PyObject *__pyx_v_data) {
- int __pyx_r;
- __Pyx_RefNannyDeclarations
- PyObject *__pyx_t_1 = NULL;
- PyObject *__pyx_t_2 = NULL;
- int __pyx_t_3;
- size_t __pyx_t_4;
- PyObject *__pyx_t_5 = NULL;
- float __pyx_t_6;
- PyObject *__pyx_t_7 = NULL;
- PyObject *__pyx_t_8 = NULL;
- __Pyx_memviewslice __pyx_t_9 = { 0, 0, { 0 }, { 0 }, { 0 } };
- __Pyx_RefNannySetupContext("__cinit__", 0);
-
- /* "silx/image/bilinear.pyx":56
- * :param data: image as a 2D array
- * """
- * assert data.ndim == 2 # <<<<<<<<<<<<<<
- * self.height = data.shape[0]
- * self.width = data.shape[1]
- */
- #ifndef CYTHON_WITHOUT_ASSERTIONS
- if (unlikely(!Py_OptimizeFlag)) {
- __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_data, __pyx_n_s_ndim); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 56, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_1);
- __pyx_t_2 = __Pyx_PyInt_EqObjC(__pyx_t_1, __pyx_int_2, 2, 0); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 56, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_2);
- __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
- __pyx_t_3 = __Pyx_PyObject_IsTrue(__pyx_t_2); if (unlikely(__pyx_t_3 < 0)) __PYX_ERR(0, 56, __pyx_L1_error)
- __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
- if (unlikely(!__pyx_t_3)) {
- PyErr_SetNone(PyExc_AssertionError);
- __PYX_ERR(0, 56, __pyx_L1_error)
- }
- }
- #endif
-
- /* "silx/image/bilinear.pyx":57
- * """
- * assert data.ndim == 2
- * self.height = data.shape[0] # <<<<<<<<<<<<<<
- * self.width = data.shape[1]
- * self.maxi = data.max()
- */
- __pyx_t_2 = __Pyx_PyObject_GetAttrStr(__pyx_v_data, __pyx_n_s_shape); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 57, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_2);
- __pyx_t_1 = __Pyx_GetItemInt(__pyx_t_2, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 57, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_1);
- __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
- __pyx_t_4 = __Pyx_PyInt_As_size_t(__pyx_t_1); if (unlikely((__pyx_t_4 == (size_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 57, __pyx_L1_error)
- __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
- __pyx_v_self->height = __pyx_t_4;
-
- /* "silx/image/bilinear.pyx":58
- * assert data.ndim == 2
- * self.height = data.shape[0]
- * self.width = data.shape[1] # <<<<<<<<<<<<<<
- * self.maxi = data.max()
- * self.mini = data.min()
- */
- __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_data, __pyx_n_s_shape); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 58, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_1);
- __pyx_t_2 = __Pyx_GetItemInt(__pyx_t_1, 1, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 58, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_2);
- __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
- __pyx_t_4 = __Pyx_PyInt_As_size_t(__pyx_t_2); if (unlikely((__pyx_t_4 == (size_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 58, __pyx_L1_error)
- __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
- __pyx_v_self->width = __pyx_t_4;
-
- /* "silx/image/bilinear.pyx":59
- * self.height = data.shape[0]
- * self.width = data.shape[1]
- * self.maxi = data.max() # <<<<<<<<<<<<<<
- * self.mini = data.min()
- * self.data = numpy.ascontiguousarray(data, dtype=numpy.float32)
- */
- __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_data, __pyx_n_s_max); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 59, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_1);
- __pyx_t_5 = NULL;
- if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_1))) {
- __pyx_t_5 = PyMethod_GET_SELF(__pyx_t_1);
- if (likely(__pyx_t_5)) {
- PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_1);
- __Pyx_INCREF(__pyx_t_5);
- __Pyx_INCREF(function);
- __Pyx_DECREF_SET(__pyx_t_1, function);
- }
- }
- __pyx_t_2 = (__pyx_t_5) ? __Pyx_PyObject_CallOneArg(__pyx_t_1, __pyx_t_5) : __Pyx_PyObject_CallNoArg(__pyx_t_1);
- __Pyx_XDECREF(__pyx_t_5); __pyx_t_5 = 0;
- if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 59, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_2);
- __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
- __pyx_t_6 = __pyx_PyFloat_AsFloat(__pyx_t_2); if (unlikely((__pyx_t_6 == (float)-1) && PyErr_Occurred())) __PYX_ERR(0, 59, __pyx_L1_error)
- __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
- __pyx_v_self->maxi = __pyx_t_6;
-
- /* "silx/image/bilinear.pyx":60
- * self.width = data.shape[1]
- * self.maxi = data.max()
- * self.mini = data.min() # <<<<<<<<<<<<<<
- * self.data = numpy.ascontiguousarray(data, dtype=numpy.float32)
- *
- */
- __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_v_data, __pyx_n_s_min); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 60, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_1);
- __pyx_t_5 = NULL;
- if (CYTHON_UNPACK_METHODS && likely(PyMethod_Check(__pyx_t_1))) {
- __pyx_t_5 = PyMethod_GET_SELF(__pyx_t_1);
- if (likely(__pyx_t_5)) {
- PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_1);
- __Pyx_INCREF(__pyx_t_5);
- __Pyx_INCREF(function);
- __Pyx_DECREF_SET(__pyx_t_1, function);
- }
- }
- __pyx_t_2 = (__pyx_t_5) ? __Pyx_PyObject_CallOneArg(__pyx_t_1, __pyx_t_5) : __Pyx_PyObject_CallNoArg(__pyx_t_1);
- __Pyx_XDECREF(__pyx_t_5); __pyx_t_5 = 0;
- if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 60, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_2);
- __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
- __pyx_t_6 = __pyx_PyFloat_AsFloat(__pyx_t_2); if (unlikely((__pyx_t_6 == (float)-1) && PyErr_Occurred())) __PYX_ERR(0, 60, __pyx_L1_error)
- __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
- __pyx_v_self->mini = __pyx_t_6;
-
- /* "silx/image/bilinear.pyx":61
- * self.maxi = data.max()
- * self.mini = data.min()
- * self.data = numpy.ascontiguousarray(data, dtype=numpy.float32) # <<<<<<<<<<<<<<
- *
- * def __dealloc__(self):
- */
- __Pyx_GetModuleGlobalName(__pyx_t_2, __pyx_n_s_numpy); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 61, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_2);
- __pyx_t_1 = __Pyx_PyObject_GetAttrStr(__pyx_t_2, __pyx_n_s_ascontiguousarray); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 61, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_1);
- __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
- __pyx_t_2 = PyTuple_New(1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 61, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_2);
- __Pyx_INCREF(__pyx_v_data);
- __Pyx_GIVEREF(__pyx_v_data);
- PyTuple_SET_ITEM(__pyx_t_2, 0, __pyx_v_data);
- __pyx_t_5 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 61, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_5);
- __Pyx_GetModuleGlobalName(__pyx_t_7, __pyx_n_s_numpy); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 61, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_7);
- __pyx_t_8 = __Pyx_PyObject_GetAttrStr(__pyx_t_7, __pyx_n_s_float32); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 61, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_8);
- __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
- if (PyDict_SetItem(__pyx_t_5, __pyx_n_s_dtype, __pyx_t_8) < 0) __PYX_ERR(0, 61, __pyx_L1_error)
- __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
- __pyx_t_8 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_t_2, __pyx_t_5); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 61, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_8);
- __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
- __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
- __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
- __pyx_t_9 = __Pyx_PyObject_to_MemoryviewSlice_d_dc_float(__pyx_t_8, PyBUF_WRITABLE); if (unlikely(!__pyx_t_9.memview)) __PYX_ERR(0, 61, __pyx_L1_error)
- __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
- __PYX_XDEC_MEMVIEW(&__pyx_v_self->data, 0);
- __pyx_v_self->data = __pyx_t_9;
- __pyx_t_9.memview = NULL;
- __pyx_t_9.data = NULL;
-
- /* "silx/image/bilinear.pyx":51
- * cdef float c_funct(self, float, float) nogil
- *
- * def __cinit__(self, data not None): # <<<<<<<<<<<<<<
- * """Constructor
- *
- */
-
- /* function exit code */
- __pyx_r = 0;
- goto __pyx_L0;
- __pyx_L1_error:;
- __Pyx_XDECREF(__pyx_t_1);
- __Pyx_XDECREF(__pyx_t_2);
- __Pyx_XDECREF(__pyx_t_5);
- __Pyx_XDECREF(__pyx_t_7);
- __Pyx_XDECREF(__pyx_t_8);
- __PYX_XDEC_MEMVIEW(&__pyx_t_9, 1);
- __Pyx_AddTraceback("silx.image.bilinear.BilinearImage.__cinit__", __pyx_clineno, __pyx_lineno, __pyx_filename);
- __pyx_r = -1;
- __pyx_L0:;
- __Pyx_RefNannyFinishContext();
- return __pyx_r;
-}
-
-/* "silx/image/bilinear.pyx":63
- * self.data = numpy.ascontiguousarray(data, dtype=numpy.float32)
- *
- * def __dealloc__(self): # <<<<<<<<<<<<<<
- * self.data = None
- *
- */
-
-/* Python wrapper */
-static void __pyx_pw_4silx_5image_8bilinear_13BilinearImage_3__dealloc__(PyObject *__pyx_v_self); /*proto*/
-static void __pyx_pw_4silx_5image_8bilinear_13BilinearImage_3__dealloc__(PyObject *__pyx_v_self) {
- __Pyx_RefNannyDeclarations
- __Pyx_RefNannySetupContext("__dealloc__ (wrapper)", 0);
- __pyx_pf_4silx_5image_8bilinear_13BilinearImage_2__dealloc__(((struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *)__pyx_v_self));
-
- /* function exit code */
- __Pyx_RefNannyFinishContext();
-}
-
-static void __pyx_pf_4silx_5image_8bilinear_13BilinearImage_2__dealloc__(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self) {
- __Pyx_RefNannyDeclarations
- __Pyx_memviewslice __pyx_t_1 = { 0, 0, { 0 }, { 0 }, { 0 } };
- __Pyx_RefNannySetupContext("__dealloc__", 0);
-
- /* "silx/image/bilinear.pyx":64
- *
- * def __dealloc__(self):
- * self.data = None # <<<<<<<<<<<<<<
- *
- * def __call__(self, coord):
- */
- __pyx_t_1 = __Pyx_PyObject_to_MemoryviewSlice_d_dc_float(Py_None, PyBUF_WRITABLE); if (unlikely(!__pyx_t_1.memview)) __PYX_ERR(0, 64, __pyx_L1_error)
- __PYX_XDEC_MEMVIEW(&__pyx_v_self->data, 0);
- __pyx_v_self->data = __pyx_t_1;
- __pyx_t_1.memview = NULL;
- __pyx_t_1.data = NULL;
-
- /* "silx/image/bilinear.pyx":63
- * self.data = numpy.ascontiguousarray(data, dtype=numpy.float32)
- *
- * def __dealloc__(self): # <<<<<<<<<<<<<<
- * self.data = None
- *
- */
-
- /* function exit code */
- goto __pyx_L0;
- __pyx_L1_error:;
- __PYX_XDEC_MEMVIEW(&__pyx_t_1, 1);
- __Pyx_WriteUnraisable("silx.image.bilinear.BilinearImage.__dealloc__", __pyx_clineno, __pyx_lineno, __pyx_filename, 1, 0);
- __pyx_L0:;
- __Pyx_RefNannyFinishContext();
-}
-
-/* "silx/image/bilinear.pyx":66
- * self.data = None
- *
- * def __call__(self, coord): # <<<<<<<<<<<<<<
- * """Function f((y, x)) where f is a continuous function
- * made from the image and (y,x)=(row, column) is the pixel coordinates
- */
-
-/* Python wrapper */
-static PyObject *__pyx_pw_4silx_5image_8bilinear_13BilinearImage_5__call__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
-static char __pyx_doc_4silx_5image_8bilinear_13BilinearImage_4__call__[] = "Function f((y, x)) where f is a continuous function\n made from the image and (y,x)=(row, column) is the pixel coordinates\n in natural C-order\n\n :param x: 2-tuple of float (row, column)\n :return: Interpolated signal from the image\n ";
-#if CYTHON_COMPILING_IN_CPYTHON
-struct wrapperbase __pyx_wrapperbase_4silx_5image_8bilinear_13BilinearImage_4__call__;
-#endif
-static PyObject *__pyx_pw_4silx_5image_8bilinear_13BilinearImage_5__call__(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
- PyObject *__pyx_v_coord = 0;
- PyObject *__pyx_r = 0;
- __Pyx_RefNannyDeclarations
- __Pyx_RefNannySetupContext("__call__ (wrapper)", 0);
- {
- static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_coord,0};
- PyObject* values[1] = {0};
- if (unlikely(__pyx_kwds)) {
- Py_ssize_t kw_args;
- const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
- switch (pos_args) {
- case 1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
- CYTHON_FALLTHROUGH;
- case 0: break;
- default: goto __pyx_L5_argtuple_error;
- }
- kw_args = PyDict_Size(__pyx_kwds);
- switch (pos_args) {
- case 0:
- if (likely((values[0] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_coord)) != 0)) kw_args--;
- else goto __pyx_L5_argtuple_error;
- }
- if (unlikely(kw_args > 0)) {
- if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "__call__") < 0)) __PYX_ERR(0, 66, __pyx_L3_error)
- }
- } else if (PyTuple_GET_SIZE(__pyx_args) != 1) {
- goto __pyx_L5_argtuple_error;
- } else {
- values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
- }
- __pyx_v_coord = values[0];
- }
- goto __pyx_L4_argument_unpacking_done;
- __pyx_L5_argtuple_error:;
- __Pyx_RaiseArgtupleInvalid("__call__", 1, 1, 1, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 66, __pyx_L3_error)
- __pyx_L3_error:;
- __Pyx_AddTraceback("silx.image.bilinear.BilinearImage.__call__", __pyx_clineno, __pyx_lineno, __pyx_filename);
- __Pyx_RefNannyFinishContext();
- return NULL;
- __pyx_L4_argument_unpacking_done:;
- __pyx_r = __pyx_pf_4silx_5image_8bilinear_13BilinearImage_4__call__(((struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *)__pyx_v_self), __pyx_v_coord);
-
- /* function exit code */
- __Pyx_RefNannyFinishContext();
- return __pyx_r;
-}
-
-static PyObject *__pyx_pf_4silx_5image_8bilinear_13BilinearImage_4__call__(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self, PyObject *__pyx_v_coord) {
- PyObject *__pyx_r = NULL;
- __Pyx_RefNannyDeclarations
- PyObject *__pyx_t_1 = NULL;
- float __pyx_t_2;
- float __pyx_t_3;
- __Pyx_RefNannySetupContext("__call__", 0);
-
- /* "silx/image/bilinear.pyx":74
- * :return: Interpolated signal from the image
- * """
- * return self.c_funct(coord[1], coord[0]) # <<<<<<<<<<<<<<
- *
- * @cython.boundscheck(False)
- */
- __Pyx_XDECREF(__pyx_r);
- __pyx_t_1 = __Pyx_GetItemInt(__pyx_v_coord, 1, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 74, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_1);
- __pyx_t_2 = __pyx_PyFloat_AsFloat(__pyx_t_1); if (unlikely((__pyx_t_2 == (float)-1) && PyErr_Occurred())) __PYX_ERR(0, 74, __pyx_L1_error)
- __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
- __pyx_t_1 = __Pyx_GetItemInt(__pyx_v_coord, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 74, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_1);
- __pyx_t_3 = __pyx_PyFloat_AsFloat(__pyx_t_1); if (unlikely((__pyx_t_3 == (float)-1) && PyErr_Occurred())) __PYX_ERR(0, 74, __pyx_L1_error)
- __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
- __pyx_t_1 = PyFloat_FromDouble(((struct __pyx_vtabstruct_4silx_5image_8bilinear_BilinearImage *)__pyx_v_self->__pyx_vtab)->c_funct(__pyx_v_self, __pyx_t_2, __pyx_t_3)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 74, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_1);
- __pyx_r = __pyx_t_1;
- __pyx_t_1 = 0;
- goto __pyx_L0;
-
- /* "silx/image/bilinear.pyx":66
- * self.data = None
- *
- * def __call__(self, coord): # <<<<<<<<<<<<<<
- * """Function f((y, x)) where f is a continuous function
- * made from the image and (y,x)=(row, column) is the pixel coordinates
- */
-
- /* function exit code */
- __pyx_L1_error:;
- __Pyx_XDECREF(__pyx_t_1);
- __Pyx_AddTraceback("silx.image.bilinear.BilinearImage.__call__", __pyx_clineno, __pyx_lineno, __pyx_filename);
- __pyx_r = NULL;
- __pyx_L0:;
- __Pyx_XGIVEREF(__pyx_r);
- __Pyx_RefNannyFinishContext();
- return __pyx_r;
-}
-
-/* "silx/image/bilinear.pyx":78
- * @cython.boundscheck(False)
- * @cython.wraparound(False)
- * cdef float c_funct(self, float x, float y) nogil: # <<<<<<<<<<<<<<
- * """Function f(x, y) where f is a continuous function
- * made from the image.
- */
-
-static float __pyx_f_4silx_5image_8bilinear_13BilinearImage_c_funct(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self, float __pyx_v_x, float __pyx_v_y) {
- float __pyx_v_d0;
- float __pyx_v_d1;
- int __pyx_v_i0;
- int __pyx_v_i1;
- int __pyx_v_j0;
- int __pyx_v_j1;
- float __pyx_v_x0;
- float __pyx_v_x1;
- float __pyx_v_y0;
- float __pyx_v_y1;
- float __pyx_v_res;
- float __pyx_r;
- double __pyx_t_1;
- double __pyx_t_2;
- float __pyx_t_3;
- double __pyx_t_4;
- int __pyx_t_5;
- int __pyx_t_6;
- Py_ssize_t __pyx_t_7;
- Py_ssize_t __pyx_t_8;
- Py_ssize_t __pyx_t_9;
- Py_ssize_t __pyx_t_10;
- Py_ssize_t __pyx_t_11;
- Py_ssize_t __pyx_t_12;
- Py_ssize_t __pyx_t_13;
- Py_ssize_t __pyx_t_14;
- Py_ssize_t __pyx_t_15;
- Py_ssize_t __pyx_t_16;
- Py_ssize_t __pyx_t_17;
- Py_ssize_t __pyx_t_18;
- Py_ssize_t __pyx_t_19;
- Py_ssize_t __pyx_t_20;
- Py_ssize_t __pyx_t_21;
- Py_ssize_t __pyx_t_22;
- Py_ssize_t __pyx_t_23;
- Py_ssize_t __pyx_t_24;
-
- /* "silx/image/bilinear.pyx":89
- * """
- * cdef:
- * float d0 = min(max(y, 0.0), (self.height - 1.0)) # <<<<<<<<<<<<<<
- * float d1 = min(max(x, 0.0), (self.width - 1.0))
- * int i0, i1, j0, j1
- */
- __pyx_t_1 = (__pyx_v_self->height - 1.0);
- __pyx_t_2 = 0.0;
- __pyx_t_3 = __pyx_v_y;
- if (((__pyx_t_2 > __pyx_t_3) != 0)) {
- __pyx_t_4 = __pyx_t_2;
- } else {
- __pyx_t_4 = __pyx_t_3;
- }
- __pyx_t_2 = __pyx_t_4;
- if (((__pyx_t_1 < __pyx_t_2) != 0)) {
- __pyx_t_4 = __pyx_t_1;
- } else {
- __pyx_t_4 = __pyx_t_2;
- }
- __pyx_v_d0 = __pyx_t_4;
-
- /* "silx/image/bilinear.pyx":90
- * cdef:
- * float d0 = min(max(y, 0.0), (self.height - 1.0))
- * float d1 = min(max(x, 0.0), (self.width - 1.0)) # <<<<<<<<<<<<<<
- * int i0, i1, j0, j1
- * float x0, x1, y0, y1, res
- */
- __pyx_t_4 = (__pyx_v_self->width - 1.0);
- __pyx_t_1 = 0.0;
- __pyx_t_3 = __pyx_v_x;
- if (((__pyx_t_1 > __pyx_t_3) != 0)) {
- __pyx_t_2 = __pyx_t_1;
- } else {
- __pyx_t_2 = __pyx_t_3;
- }
- __pyx_t_1 = __pyx_t_2;
- if (((__pyx_t_4 < __pyx_t_1) != 0)) {
- __pyx_t_2 = __pyx_t_4;
- } else {
- __pyx_t_2 = __pyx_t_1;
- }
- __pyx_v_d1 = __pyx_t_2;
-
- /* "silx/image/bilinear.pyx":94
- * float x0, x1, y0, y1, res
- *
- * x0 = floor(d0) # <<<<<<<<<<<<<<
- * x1 = ceil(d0)
- * y0 = floor(d1)
- */
- __pyx_v_x0 = floor(__pyx_v_d0);
-
- /* "silx/image/bilinear.pyx":95
- *
- * x0 = floor(d0)
- * x1 = ceil(d0) # <<<<<<<<<<<<<<
- * y0 = floor(d1)
- * y1 = ceil(d1)
- */
- __pyx_v_x1 = ceil(__pyx_v_d0);
-
- /* "silx/image/bilinear.pyx":96
- * x0 = floor(d0)
- * x1 = ceil(d0)
- * y0 = floor(d1) # <<<<<<<<<<<<<<
- * y1 = ceil(d1)
- * i0 = < int > x0
- */
- __pyx_v_y0 = floor(__pyx_v_d1);
-
- /* "silx/image/bilinear.pyx":97
- * x1 = ceil(d0)
- * y0 = floor(d1)
- * y1 = ceil(d1) # <<<<<<<<<<<<<<
- * i0 = < int > x0
- * i1 = < int > x1
- */
- __pyx_v_y1 = ceil(__pyx_v_d1);
-
- /* "silx/image/bilinear.pyx":98
- * y0 = floor(d1)
- * y1 = ceil(d1)
- * i0 = < int > x0 # <<<<<<<<<<<<<<
- * i1 = < int > x1
- * j0 = < int > y0
- */
- __pyx_v_i0 = ((int)__pyx_v_x0);
-
- /* "silx/image/bilinear.pyx":99
- * y1 = ceil(d1)
- * i0 = < int > x0
- * i1 = < int > x1 # <<<<<<<<<<<<<<
- * j0 = < int > y0
- * j1 = < int > y1
- */
- __pyx_v_i1 = ((int)__pyx_v_x1);
-
- /* "silx/image/bilinear.pyx":100
- * i0 = < int > x0
- * i1 = < int > x1
- * j0 = < int > y0 # <<<<<<<<<<<<<<
- * j1 = < int > y1
- * if (i0 == i1) and (j0 == j1):
- */
- __pyx_v_j0 = ((int)__pyx_v_y0);
-
- /* "silx/image/bilinear.pyx":101
- * i1 = < int > x1
- * j0 = < int > y0
- * j1 = < int > y1 # <<<<<<<<<<<<<<
- * if (i0 == i1) and (j0 == j1):
- * res = self.data[i0, j0]
- */
- __pyx_v_j1 = ((int)__pyx_v_y1);
-
- /* "silx/image/bilinear.pyx":102
- * j0 = < int > y0
- * j1 = < int > y1
- * if (i0 == i1) and (j0 == j1): # <<<<<<<<<<<<<<
- * res = self.data[i0, j0]
- * elif i0 == i1:
- */
- __pyx_t_6 = ((__pyx_v_i0 == __pyx_v_i1) != 0);
- if (__pyx_t_6) {
- } else {
- __pyx_t_5 = __pyx_t_6;
- goto __pyx_L4_bool_binop_done;
- }
- __pyx_t_6 = ((__pyx_v_j0 == __pyx_v_j1) != 0);
- __pyx_t_5 = __pyx_t_6;
- __pyx_L4_bool_binop_done:;
- if (__pyx_t_5) {
-
- /* "silx/image/bilinear.pyx":103
- * j1 = < int > y1
- * if (i0 == i1) and (j0 == j1):
- * res = self.data[i0, j0] # <<<<<<<<<<<<<<
- * elif i0 == i1:
- * res = (self.data[i0, j0] * (y1 - d1)) + (self.data[i0, j1] * (d1 - y0))
- */
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 103, __pyx_L1_error)}
- __pyx_t_7 = __pyx_v_i0;
- __pyx_t_8 = __pyx_v_j0;
- __pyx_v_res = (*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_7 * __pyx_v_self->data.strides[0]) )) + __pyx_t_8)) )));
-
- /* "silx/image/bilinear.pyx":102
- * j0 = < int > y0
- * j1 = < int > y1
- * if (i0 == i1) and (j0 == j1): # <<<<<<<<<<<<<<
- * res = self.data[i0, j0]
- * elif i0 == i1:
- */
- goto __pyx_L3;
- }
-
- /* "silx/image/bilinear.pyx":104
- * if (i0 == i1) and (j0 == j1):
- * res = self.data[i0, j0]
- * elif i0 == i1: # <<<<<<<<<<<<<<
- * res = (self.data[i0, j0] * (y1 - d1)) + (self.data[i0, j1] * (d1 - y0))
- * elif j0 == j1:
- */
- __pyx_t_5 = ((__pyx_v_i0 == __pyx_v_i1) != 0);
- if (__pyx_t_5) {
-
- /* "silx/image/bilinear.pyx":105
- * res = self.data[i0, j0]
- * elif i0 == i1:
- * res = (self.data[i0, j0] * (y1 - d1)) + (self.data[i0, j1] * (d1 - y0)) # <<<<<<<<<<<<<<
- * elif j0 == j1:
- * res = (self.data[i0, j0] * (x1 - d0)) + (self.data[i1, j0] * (d0 - x0))
- */
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 105, __pyx_L1_error)}
- __pyx_t_9 = __pyx_v_i0;
- __pyx_t_10 = __pyx_v_j0;
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 105, __pyx_L1_error)}
- __pyx_t_11 = __pyx_v_i0;
- __pyx_t_12 = __pyx_v_j1;
- __pyx_v_res = (((*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_9 * __pyx_v_self->data.strides[0]) )) + __pyx_t_10)) ))) * (__pyx_v_y1 - __pyx_v_d1)) + ((*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_11 * __pyx_v_self->data.strides[0]) )) + __pyx_t_12)) ))) * (__pyx_v_d1 - __pyx_v_y0)));
-
- /* "silx/image/bilinear.pyx":104
- * if (i0 == i1) and (j0 == j1):
- * res = self.data[i0, j0]
- * elif i0 == i1: # <<<<<<<<<<<<<<
- * res = (self.data[i0, j0] * (y1 - d1)) + (self.data[i0, j1] * (d1 - y0))
- * elif j0 == j1:
- */
- goto __pyx_L3;
- }
-
- /* "silx/image/bilinear.pyx":106
- * elif i0 == i1:
- * res = (self.data[i0, j0] * (y1 - d1)) + (self.data[i0, j1] * (d1 - y0))
- * elif j0 == j1: # <<<<<<<<<<<<<<
- * res = (self.data[i0, j0] * (x1 - d0)) + (self.data[i1, j0] * (d0 - x0))
- * else:
- */
- __pyx_t_5 = ((__pyx_v_j0 == __pyx_v_j1) != 0);
- if (__pyx_t_5) {
-
- /* "silx/image/bilinear.pyx":107
- * res = (self.data[i0, j0] * (y1 - d1)) + (self.data[i0, j1] * (d1 - y0))
- * elif j0 == j1:
- * res = (self.data[i0, j0] * (x1 - d0)) + (self.data[i1, j0] * (d0 - x0)) # <<<<<<<<<<<<<<
- * else:
- * res = (self.data[i0, j0] * (x1 - d0) * (y1 - d1)) \
- */
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 107, __pyx_L1_error)}
- __pyx_t_13 = __pyx_v_i0;
- __pyx_t_14 = __pyx_v_j0;
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 107, __pyx_L1_error)}
- __pyx_t_15 = __pyx_v_i1;
- __pyx_t_16 = __pyx_v_j0;
- __pyx_v_res = (((*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_13 * __pyx_v_self->data.strides[0]) )) + __pyx_t_14)) ))) * (__pyx_v_x1 - __pyx_v_d0)) + ((*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_15 * __pyx_v_self->data.strides[0]) )) + __pyx_t_16)) ))) * (__pyx_v_d0 - __pyx_v_x0)));
-
- /* "silx/image/bilinear.pyx":106
- * elif i0 == i1:
- * res = (self.data[i0, j0] * (y1 - d1)) + (self.data[i0, j1] * (d1 - y0))
- * elif j0 == j1: # <<<<<<<<<<<<<<
- * res = (self.data[i0, j0] * (x1 - d0)) + (self.data[i1, j0] * (d0 - x0))
- * else:
- */
- goto __pyx_L3;
- }
-
- /* "silx/image/bilinear.pyx":112
- * + (self.data[i1, j0] * (d0 - x0) * (y1 - d1)) \
- * + (self.data[i0, j1] * (x1 - d0) * (d1 - y0)) \
- * + (self.data[i1, j1] * (d0 - x0) * (d1 - y0)) # <<<<<<<<<<<<<<
- * return res
- *
- */
- /*else*/ {
-
- /* "silx/image/bilinear.pyx":109
- * res = (self.data[i0, j0] * (x1 - d0)) + (self.data[i1, j0] * (d0 - x0))
- * else:
- * res = (self.data[i0, j0] * (x1 - d0) * (y1 - d1)) \ # <<<<<<<<<<<<<<
- * + (self.data[i1, j0] * (d0 - x0) * (y1 - d1)) \
- * + (self.data[i0, j1] * (x1 - d0) * (d1 - y0)) \
- */
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 109, __pyx_L1_error)}
- __pyx_t_17 = __pyx_v_i0;
- __pyx_t_18 = __pyx_v_j0;
-
- /* "silx/image/bilinear.pyx":110
- * else:
- * res = (self.data[i0, j0] * (x1 - d0) * (y1 - d1)) \
- * + (self.data[i1, j0] * (d0 - x0) * (y1 - d1)) \ # <<<<<<<<<<<<<<
- * + (self.data[i0, j1] * (x1 - d0) * (d1 - y0)) \
- * + (self.data[i1, j1] * (d0 - x0) * (d1 - y0))
- */
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 110, __pyx_L1_error)}
- __pyx_t_19 = __pyx_v_i1;
- __pyx_t_20 = __pyx_v_j0;
-
- /* "silx/image/bilinear.pyx":111
- * res = (self.data[i0, j0] * (x1 - d0) * (y1 - d1)) \
- * + (self.data[i1, j0] * (d0 - x0) * (y1 - d1)) \
- * + (self.data[i0, j1] * (x1 - d0) * (d1 - y0)) \ # <<<<<<<<<<<<<<
- * + (self.data[i1, j1] * (d0 - x0) * (d1 - y0))
- * return res
- */
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 111, __pyx_L1_error)}
- __pyx_t_21 = __pyx_v_i0;
- __pyx_t_22 = __pyx_v_j1;
-
- /* "silx/image/bilinear.pyx":112
- * + (self.data[i1, j0] * (d0 - x0) * (y1 - d1)) \
- * + (self.data[i0, j1] * (x1 - d0) * (d1 - y0)) \
- * + (self.data[i1, j1] * (d0 - x0) * (d1 - y0)) # <<<<<<<<<<<<<<
- * return res
- *
- */
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 112, __pyx_L1_error)}
- __pyx_t_23 = __pyx_v_i1;
- __pyx_t_24 = __pyx_v_j1;
- __pyx_v_res = ((((((*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_17 * __pyx_v_self->data.strides[0]) )) + __pyx_t_18)) ))) * (__pyx_v_x1 - __pyx_v_d0)) * (__pyx_v_y1 - __pyx_v_d1)) + (((*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_19 * __pyx_v_self->data.strides[0]) )) + __pyx_t_20)) ))) * (__pyx_v_d0 - __pyx_v_x0)) * (__pyx_v_y1 - __pyx_v_d1))) + (((*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_21 * __pyx_v_self->data.strides[0]) )) + __pyx_t_22)) ))) * (__pyx_v_x1 - __pyx_v_d0)) * (__pyx_v_d1 - __pyx_v_y0))) + (((*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_23 * __pyx_v_self->data.strides[0]) )) + __pyx_t_24)) ))) * (__pyx_v_d0 - __pyx_v_x0)) * (__pyx_v_d1 - __pyx_v_y0)));
- }
- __pyx_L3:;
-
- /* "silx/image/bilinear.pyx":113
- * + (self.data[i0, j1] * (x1 - d0) * (d1 - y0)) \
- * + (self.data[i1, j1] * (d0 - x0) * (d1 - y0))
- * return res # <<<<<<<<<<<<<<
- *
- * @cython.boundscheck(False)
- */
- __pyx_r = __pyx_v_res;
- goto __pyx_L0;
-
- /* "silx/image/bilinear.pyx":78
- * @cython.boundscheck(False)
- * @cython.wraparound(False)
- * cdef float c_funct(self, float x, float y) nogil: # <<<<<<<<<<<<<<
- * """Function f(x, y) where f is a continuous function
- * made from the image.
- */
-
- /* function exit code */
- __pyx_L1_error:;
- __Pyx_WriteUnraisable("silx.image.bilinear.BilinearImage.c_funct", __pyx_clineno, __pyx_lineno, __pyx_filename, 1, 1);
- __pyx_r = 0;
- __pyx_L0:;
- return __pyx_r;
-}
-
-/* "silx/image/bilinear.pyx":117
- * @cython.boundscheck(False)
- * @cython.wraparound(False)
- * def opp_f(self, coord): # <<<<<<<<<<<<<<
- * """Function -f((y,x)) for peak finding via minimizer.
- *
- */
-
-/* Python wrapper */
-static PyObject *__pyx_pw_4silx_5image_8bilinear_13BilinearImage_7opp_f(PyObject *__pyx_v_self, PyObject *__pyx_v_coord); /*proto*/
-static char __pyx_doc_4silx_5image_8bilinear_13BilinearImage_6opp_f[] = "BilinearImage.opp_f(self, coord)\nFunction -f((y,x)) for peak finding via minimizer.\n\n Gives large number outside the boundaries to return into the image\n\n :param x: 2-tuple of float in natural C order, i.e (row, column)\n :return: Negative interpolated signal from the image\n ";
-static PyObject *__pyx_pw_4silx_5image_8bilinear_13BilinearImage_7opp_f(PyObject *__pyx_v_self, PyObject *__pyx_v_coord) {
- PyObject *__pyx_r = 0;
- __Pyx_RefNannyDeclarations
- __Pyx_RefNannySetupContext("opp_f (wrapper)", 0);
- __pyx_r = __pyx_pf_4silx_5image_8bilinear_13BilinearImage_6opp_f(((struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *)__pyx_v_self), ((PyObject *)__pyx_v_coord));
-
- /* function exit code */
- __Pyx_RefNannyFinishContext();
- return __pyx_r;
-}
-
-static PyObject *__pyx_pf_4silx_5image_8bilinear_13BilinearImage_6opp_f(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self, PyObject *__pyx_v_coord) {
- float __pyx_v_d0;
- float __pyx_v_d1;
- float __pyx_v_res;
- PyObject *__pyx_r = NULL;
- __Pyx_RefNannyDeclarations
- PyObject *__pyx_t_1 = NULL;
- PyObject *__pyx_t_2 = NULL;
- PyObject *__pyx_t_3 = NULL;
- PyObject *(*__pyx_t_4)(PyObject *);
- float __pyx_t_5;
- float __pyx_t_6;
- int __pyx_t_7;
- __Pyx_RefNannySetupContext("opp_f", 0);
-
- /* "silx/image/bilinear.pyx":127
- * cdef:
- * float d0, d1, res
- * d0, d1 = coord # <<<<<<<<<<<<<<
- * if d0 < 0:
- * res = self.mini + d0
- */
- if ((likely(PyTuple_CheckExact(__pyx_v_coord))) || (PyList_CheckExact(__pyx_v_coord))) {
- PyObject* sequence = __pyx_v_coord;
- Py_ssize_t size = __Pyx_PySequence_SIZE(sequence);
- if (unlikely(size != 2)) {
- if (size > 2) __Pyx_RaiseTooManyValuesError(2);
- else if (size >= 0) __Pyx_RaiseNeedMoreValuesError(size);
- __PYX_ERR(0, 127, __pyx_L1_error)
- }
- #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
- if (likely(PyTuple_CheckExact(sequence))) {
- __pyx_t_1 = PyTuple_GET_ITEM(sequence, 0);
- __pyx_t_2 = PyTuple_GET_ITEM(sequence, 1);
- } else {
- __pyx_t_1 = PyList_GET_ITEM(sequence, 0);
- __pyx_t_2 = PyList_GET_ITEM(sequence, 1);
- }
- __Pyx_INCREF(__pyx_t_1);
- __Pyx_INCREF(__pyx_t_2);
- #else
- __pyx_t_1 = PySequence_ITEM(sequence, 0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 127, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_1);
- __pyx_t_2 = PySequence_ITEM(sequence, 1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 127, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_2);
- #endif
- } else {
- Py_ssize_t index = -1;
- __pyx_t_3 = PyObject_GetIter(__pyx_v_coord); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 127, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_3);
- __pyx_t_4 = Py_TYPE(__pyx_t_3)->tp_iternext;
- index = 0; __pyx_t_1 = __pyx_t_4(__pyx_t_3); if (unlikely(!__pyx_t_1)) goto __pyx_L3_unpacking_failed;
- __Pyx_GOTREF(__pyx_t_1);
- index = 1; __pyx_t_2 = __pyx_t_4(__pyx_t_3); if (unlikely(!__pyx_t_2)) goto __pyx_L3_unpacking_failed;
- __Pyx_GOTREF(__pyx_t_2);
- if (__Pyx_IternextUnpackEndCheck(__pyx_t_4(__pyx_t_3), 2) < 0) __PYX_ERR(0, 127, __pyx_L1_error)
- __pyx_t_4 = NULL;
- __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
- goto __pyx_L4_unpacking_done;
- __pyx_L3_unpacking_failed:;
- __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
- __pyx_t_4 = NULL;
- if (__Pyx_IterFinish() == 0) __Pyx_RaiseNeedMoreValuesError(index);
- __PYX_ERR(0, 127, __pyx_L1_error)
- __pyx_L4_unpacking_done:;
- }
- __pyx_t_5 = __pyx_PyFloat_AsFloat(__pyx_t_1); if (unlikely((__pyx_t_5 == (float)-1) && PyErr_Occurred())) __PYX_ERR(0, 127, __pyx_L1_error)
- __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
- __pyx_t_6 = __pyx_PyFloat_AsFloat(__pyx_t_2); if (unlikely((__pyx_t_6 == (float)-1) && PyErr_Occurred())) __PYX_ERR(0, 127, __pyx_L1_error)
- __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
- __pyx_v_d0 = __pyx_t_5;
- __pyx_v_d1 = __pyx_t_6;
-
- /* "silx/image/bilinear.pyx":128
- * float d0, d1, res
- * d0, d1 = coord
- * if d0 < 0: # <<<<<<<<<<<<<<
- * res = self.mini + d0
- * elif d1 < 0:
- */
- __pyx_t_7 = ((__pyx_v_d0 < 0.0) != 0);
- if (__pyx_t_7) {
-
- /* "silx/image/bilinear.pyx":129
- * d0, d1 = coord
- * if d0 < 0:
- * res = self.mini + d0 # <<<<<<<<<<<<<<
- * elif d1 < 0:
- * res = self.mini + d1
- */
- __pyx_v_res = (__pyx_v_self->mini + __pyx_v_d0);
-
- /* "silx/image/bilinear.pyx":128
- * float d0, d1, res
- * d0, d1 = coord
- * if d0 < 0: # <<<<<<<<<<<<<<
- * res = self.mini + d0
- * elif d1 < 0:
- */
- goto __pyx_L5;
- }
-
- /* "silx/image/bilinear.pyx":130
- * if d0 < 0:
- * res = self.mini + d0
- * elif d1 < 0: # <<<<<<<<<<<<<<
- * res = self.mini + d1
- * elif d0 > (self.height - 1):
- */
- __pyx_t_7 = ((__pyx_v_d1 < 0.0) != 0);
- if (__pyx_t_7) {
-
- /* "silx/image/bilinear.pyx":131
- * res = self.mini + d0
- * elif d1 < 0:
- * res = self.mini + d1 # <<<<<<<<<<<<<<
- * elif d0 > (self.height - 1):
- * res = self.mini - d0 + self.height - 1
- */
- __pyx_v_res = (__pyx_v_self->mini + __pyx_v_d1);
-
- /* "silx/image/bilinear.pyx":130
- * if d0 < 0:
- * res = self.mini + d0
- * elif d1 < 0: # <<<<<<<<<<<<<<
- * res = self.mini + d1
- * elif d0 > (self.height - 1):
- */
- goto __pyx_L5;
- }
-
- /* "silx/image/bilinear.pyx":132
- * elif d1 < 0:
- * res = self.mini + d1
- * elif d0 > (self.height - 1): # <<<<<<<<<<<<<<
- * res = self.mini - d0 + self.height - 1
- * elif d1 > self.width - 1:
- */
- __pyx_t_7 = ((__pyx_v_d0 > (__pyx_v_self->height - 1)) != 0);
- if (__pyx_t_7) {
-
- /* "silx/image/bilinear.pyx":133
- * res = self.mini + d1
- * elif d0 > (self.height - 1):
- * res = self.mini - d0 + self.height - 1 # <<<<<<<<<<<<<<
- * elif d1 > self.width - 1:
- * res = self.mini - d1 + self.width - 1
- */
- __pyx_v_res = (((__pyx_v_self->mini - __pyx_v_d0) + __pyx_v_self->height) - 1.0);
-
- /* "silx/image/bilinear.pyx":132
- * elif d1 < 0:
- * res = self.mini + d1
- * elif d0 > (self.height - 1): # <<<<<<<<<<<<<<
- * res = self.mini - d0 + self.height - 1
- * elif d1 > self.width - 1:
- */
- goto __pyx_L5;
- }
-
- /* "silx/image/bilinear.pyx":134
- * elif d0 > (self.height - 1):
- * res = self.mini - d0 + self.height - 1
- * elif d1 > self.width - 1: # <<<<<<<<<<<<<<
- * res = self.mini - d1 + self.width - 1
- * else:
- */
- __pyx_t_7 = ((__pyx_v_d1 > (__pyx_v_self->width - 1)) != 0);
- if (__pyx_t_7) {
-
- /* "silx/image/bilinear.pyx":135
- * res = self.mini - d0 + self.height - 1
- * elif d1 > self.width - 1:
- * res = self.mini - d1 + self.width - 1 # <<<<<<<<<<<<<<
- * else:
- * res = self.c_funct(d1, d0)
- */
- __pyx_v_res = (((__pyx_v_self->mini - __pyx_v_d1) + __pyx_v_self->width) - 1.0);
-
- /* "silx/image/bilinear.pyx":134
- * elif d0 > (self.height - 1):
- * res = self.mini - d0 + self.height - 1
- * elif d1 > self.width - 1: # <<<<<<<<<<<<<<
- * res = self.mini - d1 + self.width - 1
- * else:
- */
- goto __pyx_L5;
- }
-
- /* "silx/image/bilinear.pyx":137
- * res = self.mini - d1 + self.width - 1
- * else:
- * res = self.c_funct(d1, d0) # <<<<<<<<<<<<<<
- * return - res
- *
- */
- /*else*/ {
- __pyx_v_res = ((struct __pyx_vtabstruct_4silx_5image_8bilinear_BilinearImage *)__pyx_v_self->__pyx_vtab)->c_funct(__pyx_v_self, __pyx_v_d1, __pyx_v_d0);
- }
- __pyx_L5:;
-
- /* "silx/image/bilinear.pyx":138
- * else:
- * res = self.c_funct(d1, d0)
- * return - res # <<<<<<<<<<<<<<
- *
- * @cython.boundscheck(False)
- */
- __Pyx_XDECREF(__pyx_r);
- __pyx_t_2 = PyFloat_FromDouble((-__pyx_v_res)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 138, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_2);
- __pyx_r = __pyx_t_2;
- __pyx_t_2 = 0;
- goto __pyx_L0;
-
- /* "silx/image/bilinear.pyx":117
- * @cython.boundscheck(False)
- * @cython.wraparound(False)
- * def opp_f(self, coord): # <<<<<<<<<<<<<<
- * """Function -f((y,x)) for peak finding via minimizer.
- *
- */
-
- /* function exit code */
- __pyx_L1_error:;
- __Pyx_XDECREF(__pyx_t_1);
- __Pyx_XDECREF(__pyx_t_2);
- __Pyx_XDECREF(__pyx_t_3);
- __Pyx_AddTraceback("silx.image.bilinear.BilinearImage.opp_f", __pyx_clineno, __pyx_lineno, __pyx_filename);
- __pyx_r = NULL;
- __pyx_L0:;
- __Pyx_XGIVEREF(__pyx_r);
- __Pyx_RefNannyFinishContext();
- return __pyx_r;
-}
-
-/* "silx/image/bilinear.pyx":143
- * @cython.wraparound(False)
- * @cython.cdivision(True)
- * def local_maxi(self, coord): # <<<<<<<<<<<<<<
- * """Return the nearest local maximum ... with sub-pixel refinement
- *
- */
-
-/* Python wrapper */
-static PyObject *__pyx_pw_4silx_5image_8bilinear_13BilinearImage_9local_maxi(PyObject *__pyx_v_self, PyObject *__pyx_v_coord); /*proto*/
-static char __pyx_doc_4silx_5image_8bilinear_13BilinearImage_8local_maxi[] = "BilinearImage.local_maxi(self, coord)\nReturn the nearest local maximum ... with sub-pixel refinement\n\n Nearest maximum search:\n steepest ascent\n\n Sub-pixel refinement:\n Second order Taylor expansion of the function;\n At the maximum, the first derivative is null\n delta = x-i = -Inverse[Hessian].gradient\n if Hessian is singular or \\|delta\\|>1: use a center of mass.\n\n :param coord: 2-tuple of scalar (row, column)\n :return: 2-tuple of float with the nearest local maximum\n ";
-static PyObject *__pyx_pw_4silx_5image_8bilinear_13BilinearImage_9local_maxi(PyObject *__pyx_v_self, PyObject *__pyx_v_coord) {
- PyObject *__pyx_r = 0;
- __Pyx_RefNannyDeclarations
- __Pyx_RefNannySetupContext("local_maxi (wrapper)", 0);
- __pyx_r = __pyx_pf_4silx_5image_8bilinear_13BilinearImage_8local_maxi(((struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *)__pyx_v_self), ((PyObject *)__pyx_v_coord));
-
- /* function exit code */
- __Pyx_RefNannyFinishContext();
- return __pyx_r;
-}
-
-static PyObject *__pyx_pf_4silx_5image_8bilinear_13BilinearImage_8local_maxi(struct __pyx_obj_4silx_5image_8bilinear_BilinearImage *__pyx_v_self, PyObject *__pyx_v_coord) {
- int __pyx_v_res;
- int __pyx_v_current0;
- int __pyx_v_current1;
- int __pyx_v_i0;
- int __pyx_v_i1;
- float __pyx_v_tmp;
- float __pyx_v_sum0;
- float __pyx_v_sum1;
- float __pyx_v_sum;
- float __pyx_v_a00;
- float __pyx_v_a01;
- float __pyx_v_a02;
- float __pyx_v_a10;
- float __pyx_v_a11;
- float __pyx_v_a12;
- float __pyx_v_a20;
- float __pyx_v_a21;
- float __pyx_v_a22;
- float __pyx_v_d00;
- float __pyx_v_d11;
- float __pyx_v_d01;
- float __pyx_v_denom;
- float __pyx_v_delta0;
- float __pyx_v_delta1;
- PyObject *__pyx_r = NULL;
- __Pyx_RefNannyDeclarations
- PyObject *__pyx_t_1 = NULL;
- PyObject *__pyx_t_2 = NULL;
- PyObject *__pyx_t_3 = NULL;
- size_t __pyx_t_4;
- int __pyx_t_5;
- int __pyx_t_6;
- Py_ssize_t __pyx_t_7;
- Py_ssize_t __pyx_t_8;
- Py_ssize_t __pyx_t_9;
- Py_ssize_t __pyx_t_10;
- Py_ssize_t __pyx_t_11;
- Py_ssize_t __pyx_t_12;
- Py_ssize_t __pyx_t_13;
- Py_ssize_t __pyx_t_14;
- Py_ssize_t __pyx_t_15;
- Py_ssize_t __pyx_t_16;
- Py_ssize_t __pyx_t_17;
- Py_ssize_t __pyx_t_18;
- Py_ssize_t __pyx_t_19;
- Py_ssize_t __pyx_t_20;
- Py_ssize_t __pyx_t_21;
- Py_ssize_t __pyx_t_22;
- Py_ssize_t __pyx_t_23;
- Py_ssize_t __pyx_t_24;
- long __pyx_t_25;
- long __pyx_t_26;
- int __pyx_t_27;
- long __pyx_t_28;
- long __pyx_t_29;
- int __pyx_t_30;
- Py_ssize_t __pyx_t_31;
- Py_ssize_t __pyx_t_32;
- __Pyx_RefNannySetupContext("local_maxi", 0);
-
- /* "silx/image/bilinear.pyx":161
- * int res, current0, current1
- * int i0, i1
- * float tmp, sum0 = 0, sum1 = 0, sum = 0 # <<<<<<<<<<<<<<
- * float a00, a01, a02, a10, a11, a12, a20, a21, a22
- * float d00, d11, d01, denom, delta0, delta1
- */
- __pyx_v_sum0 = 0.0;
- __pyx_v_sum1 = 0.0;
- __pyx_v_sum = 0.0;
-
- /* "silx/image/bilinear.pyx":164
- * float a00, a01, a02, a10, a11, a12, a20, a21, a22
- * float d00, d11, d01, denom, delta0, delta1
- * res = self.c_local_maxi(round(coord[0]) * self.width + round(coord[1])) # <<<<<<<<<<<<<<
- *
- * current0 = res // self.width
- */
- __pyx_t_1 = __Pyx_GetItemInt(__pyx_v_coord, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 164, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_1);
- __pyx_t_2 = __Pyx_PyObject_CallOneArg(__pyx_builtin_round, __pyx_t_1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 164, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_2);
- __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
- __pyx_t_1 = __Pyx_PyInt_FromSize_t(__pyx_v_self->width); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 164, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_1);
- __pyx_t_3 = PyNumber_Multiply(__pyx_t_2, __pyx_t_1); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 164, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_3);
- __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
- __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
- __pyx_t_1 = __Pyx_GetItemInt(__pyx_v_coord, 1, long, 1, __Pyx_PyInt_From_long, 0, 0, 0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 164, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_1);
- __pyx_t_2 = __Pyx_PyObject_CallOneArg(__pyx_builtin_round, __pyx_t_1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 164, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_2);
- __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
- __pyx_t_1 = PyNumber_Add(__pyx_t_3, __pyx_t_2); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 164, __pyx_L1_error)
- __Pyx_GOTREF(__pyx_t_1);
- __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
- __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
- __pyx_t_4 = __Pyx_PyInt_As_size_t(__pyx_t_1); if (unlikely((__pyx_t_4 == (size_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 164, __pyx_L1_error)
- __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
- __pyx_v_res = ((struct __pyx_vtabstruct_4silx_5image_8bilinear_BilinearImage *)__pyx_v_self->__pyx_vtab)->c_local_maxi(__pyx_v_self, __pyx_t_4);
-
- /* "silx/image/bilinear.pyx":166
- * res = self.c_local_maxi(round(coord[0]) * self.width + round(coord[1]))
- *
- * current0 = res // self.width # <<<<<<<<<<<<<<
- * current1 = res % self.width
- * if (current0 > 0) and (current0 < self.height - 1) and (current1 > 0) and (current1 < self.width - 1):
- */
- __pyx_v_current0 = (__pyx_v_res / __pyx_v_self->width);
-
- /* "silx/image/bilinear.pyx":167
- *
- * current0 = res // self.width
- * current1 = res % self.width # <<<<<<<<<<<<<<
- * if (current0 > 0) and (current0 < self.height - 1) and (current1 > 0) and (current1 < self.width - 1):
- * # Use second order polynomial Taylor expansion
- */
- __pyx_v_current1 = (__pyx_v_res % __pyx_v_self->width);
-
- /* "silx/image/bilinear.pyx":168
- * current0 = res // self.width
- * current1 = res % self.width
- * if (current0 > 0) and (current0 < self.height - 1) and (current1 > 0) and (current1 < self.width - 1): # <<<<<<<<<<<<<<
- * # Use second order polynomial Taylor expansion
- * a00 = self.data[current0 - 1, current1 - 1]
- */
- __pyx_t_6 = ((__pyx_v_current0 > 0) != 0);
- if (__pyx_t_6) {
- } else {
- __pyx_t_5 = __pyx_t_6;
- goto __pyx_L4_bool_binop_done;
- }
- __pyx_t_6 = ((__pyx_v_current0 < (__pyx_v_self->height - 1)) != 0);
- if (__pyx_t_6) {
- } else {
- __pyx_t_5 = __pyx_t_6;
- goto __pyx_L4_bool_binop_done;
- }
- __pyx_t_6 = ((__pyx_v_current1 > 0) != 0);
- if (__pyx_t_6) {
- } else {
- __pyx_t_5 = __pyx_t_6;
- goto __pyx_L4_bool_binop_done;
- }
- __pyx_t_6 = ((__pyx_v_current1 < (__pyx_v_self->width - 1)) != 0);
- __pyx_t_5 = __pyx_t_6;
- __pyx_L4_bool_binop_done:;
- if (__pyx_t_5) {
-
- /* "silx/image/bilinear.pyx":170
- * if (current0 > 0) and (current0 < self.height - 1) and (current1 > 0) and (current1 < self.width - 1):
- * # Use second order polynomial Taylor expansion
- * a00 = self.data[current0 - 1, current1 - 1] # <<<<<<<<<<<<<<
- * a01 = self.data[current0 - 1, current1 ]
- * a02 = self.data[current0 - 1, current1 + 1]
- */
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 170, __pyx_L1_error)}
- __pyx_t_7 = (__pyx_v_current0 - 1);
- __pyx_t_8 = (__pyx_v_current1 - 1);
- __pyx_v_a00 = (*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_7 * __pyx_v_self->data.strides[0]) )) + __pyx_t_8)) )));
-
- /* "silx/image/bilinear.pyx":171
- * # Use second order polynomial Taylor expansion
- * a00 = self.data[current0 - 1, current1 - 1]
- * a01 = self.data[current0 - 1, current1 ] # <<<<<<<<<<<<<<
- * a02 = self.data[current0 - 1, current1 + 1]
- * a10 = self.data[current0 , current1 - 1]
- */
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 171, __pyx_L1_error)}
- __pyx_t_9 = (__pyx_v_current0 - 1);
- __pyx_t_10 = __pyx_v_current1;
- __pyx_v_a01 = (*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_9 * __pyx_v_self->data.strides[0]) )) + __pyx_t_10)) )));
-
- /* "silx/image/bilinear.pyx":172
- * a00 = self.data[current0 - 1, current1 - 1]
- * a01 = self.data[current0 - 1, current1 ]
- * a02 = self.data[current0 - 1, current1 + 1] # <<<<<<<<<<<<<<
- * a10 = self.data[current0 , current1 - 1]
- * a11 = self.data[current0 , current1 ]
- */
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 172, __pyx_L1_error)}
- __pyx_t_11 = (__pyx_v_current0 - 1);
- __pyx_t_12 = (__pyx_v_current1 + 1);
- __pyx_v_a02 = (*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_11 * __pyx_v_self->data.strides[0]) )) + __pyx_t_12)) )));
-
- /* "silx/image/bilinear.pyx":173
- * a01 = self.data[current0 - 1, current1 ]
- * a02 = self.data[current0 - 1, current1 + 1]
- * a10 = self.data[current0 , current1 - 1] # <<<<<<<<<<<<<<
- * a11 = self.data[current0 , current1 ]
- * a12 = self.data[current0 , current1 + 1]
- */
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 173, __pyx_L1_error)}
- __pyx_t_13 = __pyx_v_current0;
- __pyx_t_14 = (__pyx_v_current1 - 1);
- __pyx_v_a10 = (*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_13 * __pyx_v_self->data.strides[0]) )) + __pyx_t_14)) )));
-
- /* "silx/image/bilinear.pyx":174
- * a02 = self.data[current0 - 1, current1 + 1]
- * a10 = self.data[current0 , current1 - 1]
- * a11 = self.data[current0 , current1 ] # <<<<<<<<<<<<<<
- * a12 = self.data[current0 , current1 + 1]
- * a20 = self.data[current0 + 1, current1 - 1]
- */
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 174, __pyx_L1_error)}
- __pyx_t_15 = __pyx_v_current0;
- __pyx_t_16 = __pyx_v_current1;
- __pyx_v_a11 = (*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_15 * __pyx_v_self->data.strides[0]) )) + __pyx_t_16)) )));
-
- /* "silx/image/bilinear.pyx":175
- * a10 = self.data[current0 , current1 - 1]
- * a11 = self.data[current0 , current1 ]
- * a12 = self.data[current0 , current1 + 1] # <<<<<<<<<<<<<<
- * a20 = self.data[current0 + 1, current1 - 1]
- * a21 = self.data[current0 + 1, current1 ]
- */
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 175, __pyx_L1_error)}
- __pyx_t_17 = __pyx_v_current0;
- __pyx_t_18 = (__pyx_v_current1 + 1);
- __pyx_v_a12 = (*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_17 * __pyx_v_self->data.strides[0]) )) + __pyx_t_18)) )));
-
- /* "silx/image/bilinear.pyx":176
- * a11 = self.data[current0 , current1 ]
- * a12 = self.data[current0 , current1 + 1]
- * a20 = self.data[current0 + 1, current1 - 1] # <<<<<<<<<<<<<<
- * a21 = self.data[current0 + 1, current1 ]
- * a22 = self.data[current0 + 1, current1 - 1]
- */
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 176, __pyx_L1_error)}
- __pyx_t_19 = (__pyx_v_current0 + 1);
- __pyx_t_20 = (__pyx_v_current1 - 1);
- __pyx_v_a20 = (*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_19 * __pyx_v_self->data.strides[0]) )) + __pyx_t_20)) )));
-
- /* "silx/image/bilinear.pyx":177
- * a12 = self.data[current0 , current1 + 1]
- * a20 = self.data[current0 + 1, current1 - 1]
- * a21 = self.data[current0 + 1, current1 ] # <<<<<<<<<<<<<<
- * a22 = self.data[current0 + 1, current1 - 1]
- * d00 = a12 - 2.0 * a11 + a10
- */
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 177, __pyx_L1_error)}
- __pyx_t_21 = (__pyx_v_current0 + 1);
- __pyx_t_22 = __pyx_v_current1;
- __pyx_v_a21 = (*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_21 * __pyx_v_self->data.strides[0]) )) + __pyx_t_22)) )));
-
- /* "silx/image/bilinear.pyx":178
- * a20 = self.data[current0 + 1, current1 - 1]
- * a21 = self.data[current0 + 1, current1 ]
- * a22 = self.data[current0 + 1, current1 - 1] # <<<<<<<<<<<<<<
- * d00 = a12 - 2.0 * a11 + a10
- * d11 = a21 - 2.0 * a11 + a01
- */
- if (unlikely(!__pyx_v_self->data.memview)) {PyErr_SetString(PyExc_AttributeError,"Memoryview is not initialized");__PYX_ERR(0, 178, __pyx_L1_error)}
- __pyx_t_23 = (__pyx_v_current0 + 1);
- __pyx_t_24 = (__pyx_v_current1 - 1);
- __pyx_v_a22 = (*((float *) ( /* dim=1 */ ((char *) (((float *) ( /* dim=0 */ (__pyx_v_self->data.data + __pyx_t_23 * __pyx_v_self->data.strides[0]) )) + __pyx_t_24)) )));
-
- /* "silx/image/bilinear.pyx":179
- * a21 = self.data[current0 + 1, current1 ]
- * a22 = self.data[current0 + 1, current1 - 1]
- * d00 = a12 - 2.0 * a11 + a10 # <<<<<<<<<<<<<<
- * d11 = a21 - 2.0 * a11 + a01
- * d01 = (a00 - a02 - a20 + a22) / 4.0
- */
- __pyx_v_d00 = ((__pyx_v_a12 - (2.0 * __pyx_v_a11)) + __pyx_v_a10);
-
- /* "silx/image/bilinear.pyx":180
- * a22 = self.data[current0 + 1, current1 - 1]
- * d00 = a12 - 2.0 * a11 + a10
- * d11 = a21 - 2.0 * a11 + a01 # <<<<<<<<<<<<<<
- * d01 = (a00 - a02 - a20 + a22) / 4.0
- * denom = 2.0 * (d00 * d11 - d01 * d01)
- */
- __pyx_v_d11 = ((__pyx_v_a21 - (2.0 * __pyx_v_a11)) + __pyx_v_a01);
-
- /* "silx/image/bilinear.pyx":181
- * d00 = a12 - 2.0 * a11 + a10
- * d11 = a21 - 2.0 * a11 + a01
- * d01 = (a00 - a02 - a20 + a22) / 4.0 # <<<<<<<<<<<<<<
- * denom = 2.0 * (d00 * d11 - d01 * d01)
- * if abs(denom) < 1e-10:
- */
- __pyx_v_d01 = (((double)(((__pyx_v_a00 - __pyx_v_a02) - __pyx_v_a20) + __pyx_v_a22)) / 4.0);
-
- /* "silx/image/bilinear.pyx":182
- * d11 = a21 - 2.0 * a11 + a01
- * d01 = (a00 - a02 - a20 + a22) / 4.0
- * denom = 2.0 * (d00 * d11 - d01 * d01) # <<<<<<<<<<<<<<
- * if abs(denom) < 1e-10:
- * logger.debug("Singular determinant, Hessian undefined")
- */
- __pyx_v_denom = (2.0 * ((__pyx_v_d00 * __pyx_v_d11) - (__pyx_v_d01 * __pyx_v_d01)));
-
- /* "silx/image/bilinear.pyx":183
- * d01 = (a00 - a02 - a20 + a22) /