diff options
author | Picca Frédéric-Emmanuel <picca@synchrotron-soleil.fr> | 2019-12-23 13:45:09 +0100 |
---|---|---|
committer | Picca Frédéric-Emmanuel <picca@synchrotron-soleil.fr> | 2019-12-23 13:45:09 +0100 |
commit | 5d647cf9a6159afd2933da594b9c79ad93d3cd9b (patch) | |
tree | 2571025a602f68fc8933b01104dc712d41f84034 | |
parent | 654a6ac93513c3cc1ef97cacd782ff674c6f4559 (diff) |
New upstream version 0.12.0~b0+dfsg
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 ------------------ @@ -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 Binary files differnew file mode 100644 index 0000000..49618b5 --- /dev/null +++ b/doc/source/sample_code/img/compareImages.png diff --git a/doc/source/sample_code/img/compositeline.png b/doc/source/sample_code/img/compositeline.png Binary files differnew file mode 100644 index 0000000..8950286 --- /dev/null +++ b/doc/source/sample_code/img/compositeline.png diff --git a/doc/source/sample_code/img/dropZones.png b/doc/source/sample_code/img/dropZones.png Binary files differnew file mode 100644 index 0000000..3196c6b --- /dev/null +++ b/doc/source/sample_code/img/dropZones.png diff --git a/doc/source/sample_code/img/exampleBaseline.png b/doc/source/sample_code/img/exampleBaseline.png Binary files differnew file mode 100644 index 0000000..fcd5778 --- /dev/null +++ b/doc/source/sample_code/img/exampleBaseline.png diff --git a/doc/source/sample_code/img/findContours.png b/doc/source/sample_code/img/findContours.png Binary files differnew file mode 100644 index 0000000..06dcf01 --- /dev/null +++ b/doc/source/sample_code/img/findContours.png diff --git a/doc/source/sample_code/img/plotCurveLegendWidget.png b/doc/source/sample_code/img/plotCurveLegendWidget.png Binary files differnew file mode 100644 index 0000000..d857950 --- /dev/null +++ b/doc/source/sample_code/img/plotCurveLegendWidget.png diff --git a/doc/source/sample_code/img/plotStats.png b/doc/source/sample_code/img/plotStats.png Binary files differnew file mode 100644 index 0000000..e4fc781 --- /dev/null +++ b/doc/source/sample_code/img/plotStats.png diff --git a/doc/source/sample_code/img/scatterview.png b/doc/source/sample_code/img/scatterview.png Binary files differnew file mode 100644 index 0000000..32f0235 --- /dev/null +++ b/doc/source/sample_code/img/scatterview.png diff --git a/doc/source/sample_code/img/syncPlotLocation.png b/doc/source/sample_code/img/syncPlotLocation.png Binary files differnew file mode 100644 index 0000000..0b86f3e --- /dev/null +++ b/doc/source/sample_code/img/syncPlotLocation.png 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 @@ -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] |