summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.rst56
-rw-r--r--LICENSE29
-rw-r--r--MANIFEST.in5
-rw-r--r--PKG-INFO87
-rw-r--r--README.rst87
-rwxr-xr-xbuild-deb.sh239
-rw-r--r--copyright41
-rw-r--r--doc/source/Tutorials/fitconfig.rst2
-rw-r--r--doc/source/Tutorials/img/silx_view_edf.pngbin0 -> 42917 bytes
-rw-r--r--doc/source/Tutorials/io.rst289
-rw-r--r--doc/source/Tutorials/specfile_to_hdf5.rst57
-rw-r--r--doc/source/conf.py59
-rw-r--r--doc/source/description/img/sift_frame_ROI.pngbin0 -> 57727 bytes
-rw-r--r--doc/source/description/img/sift_match2.pngbin0 -> 496462 bytes
-rw-r--r--doc/source/description/sift.rst4
-rw-r--r--doc/source/ext/sphinxext-archive.py184
-rw-r--r--doc/source/index.rst15
-rw-r--r--doc/source/license.rst4
-rw-r--r--doc/source/modules/gui/data/img/ArrayTableWidget.pngbin0 -> 29088 bytes
-rw-r--r--doc/source/modules/gui/fit/backgroundwidget.rst7
-rw-r--r--doc/source/modules/gui/fit/img/BackgroundDialog.pngbin0 -> 52076 bytes
-rw-r--r--doc/source/modules/gui/fit/img/FitWidget.pngbin0 -> 43479 bytes
-rw-r--r--doc/source/modules/gui/fit/img/bgwidget.pngbin68544 -> 0 bytes
-rw-r--r--doc/source/modules/gui/fit/index.rst4
-rw-r--r--doc/source/modules/gui/gallery.rst273
-rw-r--r--doc/source/modules/gui/icons.rst54
-rw-r--r--doc/source/modules/gui/img/IPythonDockWidget.pngbin0 -> 27683 bytes
-rw-r--r--doc/source/modules/gui/img/IPythonWidget.pngbin0 -> 28980 bytes
-rw-r--r--doc/source/modules/gui/index.rst8
-rw-r--r--doc/source/modules/gui/plot/actions/control.rst5
-rw-r--r--doc/source/modules/gui/plot/actions/examples.rst (renamed from doc/source/modules/gui/plot/plotactions_examples.rst)6
-rw-r--r--doc/source/modules/gui/plot/actions/fit.rst5
-rw-r--r--doc/source/modules/gui/plot/actions/histogram.rst5
-rw-r--r--doc/source/modules/gui/plot/actions/img/fftAction0.png (renamed from doc/source/modules/gui/plot/img/fftAction0.png)bin91165 -> 91165 bytes
-rw-r--r--doc/source/modules/gui/plot/actions/img/fftAction1.png (renamed from doc/source/modules/gui/plot/img/fftAction1.png)bin38847 -> 38847 bytes
-rw-r--r--doc/source/modules/gui/plot/actions/img/shiftAction0.png (renamed from doc/source/modules/gui/plot/img/shiftAction0.png)bin29092 -> 29092 bytes
-rw-r--r--doc/source/modules/gui/plot/actions/img/shiftAction3.png (renamed from doc/source/modules/gui/plot/img/shiftAction3.png)bin25949 -> 25949 bytes
-rw-r--r--doc/source/modules/gui/plot/actions/index.rst30
-rw-r--r--doc/source/modules/gui/plot/actions/io.rst6
-rw-r--r--doc/source/modules/gui/plot/actions/medfilt.rst5
-rw-r--r--doc/source/modules/gui/plot/colormap.rst16
-rw-r--r--doc/source/modules/gui/plot/compleximageview.rst19
-rw-r--r--doc/source/modules/gui/plot/dev.rst20
-rw-r--r--doc/source/modules/gui/plot/getting_started.rst52
-rw-r--r--doc/source/modules/gui/plot/imageview.rst6
-rw-r--r--doc/source/modules/gui/plot/img/ComplexImageView.pngbin0 -> 72268 bytes
-rw-r--r--doc/source/modules/gui/plot/img/printPreviewMultiPlot.pngbin0 -> 25948 bytes
-rw-r--r--doc/source/modules/gui/plot/index.rst92
-rw-r--r--doc/source/modules/gui/plot/items.rst83
-rw-r--r--doc/source/modules/gui/plot/plot.rst16
-rw-r--r--doc/source/modules/gui/plot/plotactions.rst20
-rw-r--r--doc/source/modules/gui/plot/plotwidget.rst138
-rw-r--r--doc/source/modules/gui/plot/printpreviewtoolbutton.rst8
-rw-r--r--doc/source/modules/gui/plot/stackview.rst4
-rw-r--r--doc/source/modules/gui/plot3d/actions.rst17
-rw-r--r--doc/source/modules/gui/plot3d/dev.rst8
-rw-r--r--doc/source/modules/gui/plot3d/index.rst39
-rw-r--r--doc/source/modules/gui/plot3d/scalarfieldview.rst7
-rw-r--r--doc/source/modules/gui/plot3d/toolbars.rst29
-rw-r--r--doc/source/modules/gui/plot3d/tools.rst40
-rw-r--r--doc/source/modules/gui/widgets/img/FrameBrowser.pngbin0 -> 3731 bytes
-rw-r--r--doc/source/modules/gui/widgets/img/HorizontalSliderWithBrowser.pngbin0 -> 4215 bytes
-rw-r--r--doc/source/modules/gui/widgets/img/PeriodicCombo.pngbin0 -> 3464 bytes
-rw-r--r--doc/source/modules/gui/widgets/img/PeriodicList.pngbin0 -> 12035 bytes
-rw-r--r--doc/source/modules/gui/widgets/img/PeriodicTable.pngbin0 -> 35124 bytes
-rw-r--r--doc/source/modules/gui/widgets/img/TableWidget.pngbin0 -> 4058 bytes
-rw-r--r--doc/source/modules/gui/widgets/img/ThreadPoolPushButton.pngbin0 -> 2729 bytes
-rw-r--r--doc/source/modules/gui/widgets/img/WaitingPushButton.pngbin0 -> 1962 bytes
-rw-r--r--doc/source/modules/gui/widgets/index.rst1
-rw-r--r--doc/source/modules/gui/widgets/printpreview.rst60
-rw-r--r--doc/source/modules/image/backprojection.rst8
-rw-r--r--doc/source/modules/image/index.rst1
-rw-r--r--doc/source/modules/image/projection.rst15
-rw-r--r--doc/source/modules/image/reconstruction.rst14
-rw-r--r--doc/source/modules/io/convert.rst8
-rw-r--r--doc/source/modules/io/index.rst7
-rw-r--r--doc/source/modules/io/spech5.rst36
-rw-r--r--doc/source/modules/io/spectoh5.rst8
-rw-r--r--doc/source/modules/utils/decorators.rst6
-rw-r--r--doc/source/sample_code/img/animatedicons.pngbin0 -> 7594 bytes
-rw-r--r--doc/source/sample_code/img/customHdf5TreeModel.pngbin0 -> 51980 bytes
-rw-r--r--doc/source/sample_code/img/fftPlotAction.pngbin0 -> 105383 bytes
-rw-r--r--doc/source/sample_code/img/hdf5widget.pngbin0 -> 58367 bytes
-rw-r--r--doc/source/sample_code/img/icons.pngbin0 -> 103832 bytes
-rw-r--r--doc/source/sample_code/img/imageview.pngbin0 -> 49273 bytes
-rw-r--r--doc/source/sample_code/img/periodicTable.pngbin0 -> 33409 bytes
-rw-r--r--doc/source/sample_code/img/plotContextMenu.pngbin0 -> 32690 bytes
-rw-r--r--doc/source/sample_code/img/plotItemsSelector.pngbin0 -> 26989 bytes
-rw-r--r--doc/source/sample_code/img/plotLimits.pngbin0 -> 151285 bytes
-rw-r--r--doc/source/sample_code/img/plotUpdateFromThread.pngbin0 -> 62761 bytes
-rw-r--r--doc/source/sample_code/img/plotWidget.pngbin0 -> 78293 bytes
-rw-r--r--doc/source/sample_code/img/printPreview.pngbin0 -> 37076 bytes
-rw-r--r--doc/source/sample_code/img/scatterMask.pngbin0 -> 44803 bytes
-rw-r--r--doc/source/sample_code/img/shiftPlotAction.pngbin0 -> 38559 bytes
-rw-r--r--doc/source/sample_code/img/simplewidget.pngbin0 -> 9276 bytes
-rw-r--r--doc/source/sample_code/img/stackView.pngbin0 -> 56329 bytes
-rw-r--r--doc/source/sample_code/img/syncaxis.pngbin0 -> 133659 bytes
-rw-r--r--doc/source/sample_code/img/viewer3DVolume.pngbin0 -> 58235 bytes
-rw-r--r--doc/source/sample_code/index.rst232
-rw-r--r--doc/source/tutorials.rst12
-rw-r--r--examples/customHdf5TreeModel.py290
-rwxr-xr-xexamples/fftPlotAction.py20
-rw-r--r--examples/icons.py10
-rw-r--r--examples/plotContextMenu.py100
-rwxr-xr-x[-rw-r--r--]examples/plotItemsSelector.py (renamed from examples/colorbar.py)50
-rw-r--r--examples/plotLimits.py93
-rw-r--r--examples/plotUpdateFromThread.py128
-rw-r--r--examples/plotWidget.py121
-rwxr-xr-xexamples/printPreview.py82
-rwxr-xr-xexamples/shiftPlotAction.py2
-rw-r--r--examples/syncaxis.py101
-rw-r--r--examples/viewer3DVolume.py27
-rw-r--r--[-rwxr-xr-x]examples/writetoh5.py (renamed from examples/spectoh5.py)20
-rw-r--r--package/debian8/changelog22
-rw-r--r--package/debian8/clean1
-rw-r--r--package/debian8/compat1
-rw-r--r--package/debian8/control198
-rw-r--r--package/debian8/gbp.conf2
-rw-r--r--package/debian8/python-silx-doc.doc-base9
-rwxr-xr-xpackage/debian8/rules54
-rw-r--r--package/debian8/source/format1
-rw-r--r--package/debian8/source/options1
-rw-r--r--package/debian8/watch5
-rw-r--r--package/debian9/changelog22
-rw-r--r--package/debian9/clean1
-rw-r--r--package/debian9/compat1
-rw-r--r--package/debian9/control125
-rw-r--r--package/debian9/gbp.conf2
-rw-r--r--package/debian9/python-silx-doc.doc-base9
-rwxr-xr-xpackage/debian9/rules50
-rw-r--r--package/debian9/source/format1
-rw-r--r--package/debian9/source/options1
-rw-r--r--package/debian9/watch5
-rw-r--r--package/desktop/org.silx.SilxView.desktop9
-rw-r--r--package/desktop/silx.pngbin0 -> 3172 bytes
-rw-r--r--package/desktop/silx.svg118
-rw-r--r--requirements-dev.txt7
-rw-r--r--requirements.txt29
-rwxr-xr-xrun_tests.py84
-rw-r--r--setup.cfg1
-rw-r--r--setup.py74
-rw-r--r--silx.egg-info/PKG-INFO87
-rw-r--r--silx.egg-info/SOURCES.txt338
-rw-r--r--silx/__init__.py7
-rw-r--r--silx/__main__.py16
-rw-r--r--silx/app/convert.py283
-rw-r--r--silx/app/qtutils.py243
-rw-r--r--silx/app/test/__init__.py10
-rw-r--r--silx/app/test/test_convert.py182
-rw-r--r--silx/app/test/test_view.py33
-rw-r--r--silx/app/test_.py175
-rw-r--r--silx/app/view.py184
-rw-r--r--silx/gui/_glutils/FramebufferTexture.py78
-rw-r--r--silx/gui/_glutils/OpenGLWidget.py409
-rw-r--r--silx/gui/_glutils/VertexBuffer.py10
-rw-r--r--silx/gui/_glutils/__init__.py1
-rw-r--r--silx/gui/_glutils/font.py32
-rw-r--r--silx/gui/console.py4
-rw-r--r--silx/gui/data/ArrayTableModel.py7
-rw-r--r--silx/gui/data/ArrayTableWidget.py2
-rw-r--r--silx/gui/data/DataViewer.py10
-rw-r--r--silx/gui/data/DataViewerFrame.py12
-rw-r--r--silx/gui/data/DataViews.py209
-rw-r--r--silx/gui/data/Hdf5TableView.py76
-rw-r--r--silx/gui/data/HexaTableView.py278
-rw-r--r--silx/gui/data/NXdataWidgets.py22
-rw-r--r--silx/gui/data/RecordTableView.py10
-rw-r--r--silx/gui/data/TextFormatter.py168
-rw-r--r--silx/gui/data/test/test_dataviewer.py28
-rw-r--r--silx/gui/data/test/test_textformatter.py113
-rw-r--r--silx/gui/fit/BackgroundWidget.py16
-rw-r--r--silx/gui/fit/FitConfig.py6
-rw-r--r--silx/gui/fit/FitWidget.py2
-rw-r--r--silx/gui/hdf5/Hdf5Formatter.py229
-rw-r--r--silx/gui/hdf5/Hdf5HeaderView.py29
-rw-r--r--silx/gui/hdf5/Hdf5Item.py182
-rw-r--r--silx/gui/hdf5/Hdf5Node.py29
-rw-r--r--silx/gui/hdf5/Hdf5TreeModel.py78
-rw-r--r--silx/gui/hdf5/Hdf5TreeView.py85
-rw-r--r--silx/gui/hdf5/NexusSortFilterProxyModel.py5
-rw-r--r--silx/gui/hdf5/_utils.py184
-rw-r--r--silx/gui/hdf5/test/_mock.py130
-rw-r--r--silx/gui/hdf5/test/test_hdf5.py454
-rw-r--r--silx/gui/icons.py54
-rw-r--r--silx/gui/plot/ColorBar.py456
-rw-r--r--silx/gui/plot/Colormap.py410
-rw-r--r--silx/gui/plot/ColormapDialog.py78
-rw-r--r--silx/gui/plot/Colors.py217
-rw-r--r--silx/gui/plot/ComplexImageView.py670
-rw-r--r--silx/gui/plot/CurvesROIWidget.py20
-rw-r--r--silx/gui/plot/ImageView.py157
-rw-r--r--silx/gui/plot/ItemsSelectionDialog.py282
-rw-r--r--silx/gui/plot/LegendSelector.py21
-rw-r--r--silx/gui/plot/LimitsHistory.py83
-rw-r--r--silx/gui/plot/MPLColormap.py1062
-rw-r--r--silx/gui/plot/MaskToolsWidget.py87
-rw-r--r--silx/gui/plot/Plot.py2925
-rw-r--r--silx/gui/plot/PlotActions.py1399
-rw-r--r--silx/gui/plot/PlotInteraction.py79
-rw-r--r--silx/gui/plot/PlotToolButtons.py13
-rw-r--r--silx/gui/plot/PlotTools.py152
-rw-r--r--silx/gui/plot/PlotWidget.py3020
-rw-r--r--silx/gui/plot/PlotWindow.py193
-rw-r--r--silx/gui/plot/PrintPreviewToolButton.py350
-rw-r--r--silx/gui/plot/Profile.py77
-rw-r--r--silx/gui/plot/ScatterMaskToolsWidget.py13
-rw-r--r--silx/gui/plot/StackView.py479
-rw-r--r--silx/gui/plot/_BaseMaskToolsWidget.py124
-rw-r--r--silx/gui/plot/__init__.py7
-rw-r--r--silx/gui/plot/_utils/__init__.py11
-rw-r--r--silx/gui/plot/_utils/panzoom.py154
-rw-r--r--silx/gui/plot/actions/PlotAction.py79
-rw-r--r--silx/gui/plot/actions/__init__.py38
-rw-r--r--silx/gui/plot/actions/control.py549
-rw-r--r--silx/gui/plot/actions/fit.py189
-rw-r--r--silx/gui/plot/actions/histogram.py170
-rw-r--r--silx/gui/plot/actions/io.py538
-rw-r--r--silx/gui/plot/actions/medfilt.py145
-rw-r--r--silx/gui/plot/actions/mode.py100
-rw-r--r--silx/gui/plot/backends/BackendBase.py39
-rw-r--r--silx/gui/plot/backends/BackendMatplotlib.py104
-rw-r--r--silx/gui/plot/backends/BackendOpenGL.py144
-rw-r--r--silx/gui/plot/backends/glutils/GLPlotCurve.py2
-rw-r--r--silx/gui/plot/backends/glutils/GLPlotFrame.py24
-rw-r--r--silx/gui/plot/backends/glutils/GLPlotImage.py56
-rw-r--r--silx/gui/plot/backends/glutils/GLText.py43
-rw-r--r--silx/gui/plot/backends/glutils/PlotImageFile.py14
-rw-r--r--silx/gui/plot/items/__init__.py13
-rw-r--r--silx/gui/plot/items/axis.py477
-rw-r--r--silx/gui/plot/items/core.py182
-rw-r--r--silx/gui/plot/items/curve.py18
-rw-r--r--silx/gui/plot/items/histogram.py36
-rw-r--r--silx/gui/plot/items/image.py75
-rw-r--r--silx/gui/plot/items/marker.py11
-rw-r--r--silx/gui/plot/items/scatter.py10
-rw-r--r--silx/gui/plot/items/shape.py14
-rw-r--r--silx/gui/plot/matplotlib/Colormap.py282
-rw-r--r--silx/gui/plot/matplotlib/ModestImage.py (renamed from silx/gui/plot/backends/ModestImage.py)2
-rw-r--r--silx/gui/plot/matplotlib/__init__.py (renamed from silx/gui/plot/backends/_matplotlib.py)14
-rw-r--r--silx/gui/plot/setup.py5
-rw-r--r--silx/gui/plot/test/__init__.py80
-rw-r--r--silx/gui/plot/test/testColorBar.py227
-rw-r--r--silx/gui/plot/test/testColormap.py291
-rw-r--r--silx/gui/plot/test/testColors.py8
-rw-r--r--silx/gui/plot/test/testComplexImageView.py95
-rw-r--r--silx/gui/plot/test/testCurvesROIWidget.py3
-rw-r--r--silx/gui/plot/test/testItem.py231
-rw-r--r--silx/gui/plot/test/testLegendSelector.py3
-rw-r--r--silx/gui/plot/test/testLimitConstraints.py125
-rw-r--r--silx/gui/plot/test/testMaskToolsWidget.py38
-rw-r--r--silx/gui/plot/test/testPlotInteraction.py27
-rw-r--r--silx/gui/plot/test/testPlotTools.py23
-rw-r--r--silx/gui/plot/test/testPlotWidget.py775
-rw-r--r--silx/gui/plot/test/testPlotWidgetNoBackend.py (renamed from silx/gui/plot/test/testPlot.py)98
-rw-r--r--silx/gui/plot/test/testPlotWindow.py14
-rw-r--r--silx/gui/plot/test/testScatterMaskToolsWidget.py37
-rw-r--r--silx/gui/plot/test/testStackView.py33
-rw-r--r--silx/gui/plot/test/testUtilsAxis.py148
-rw-r--r--silx/gui/plot/test/utils.py194
-rw-r--r--silx/gui/plot/utils/__init__.py30
-rw-r--r--silx/gui/plot/utils/axis.py164
-rw-r--r--silx/gui/plot3d/Plot3DWidget.py203
-rw-r--r--silx/gui/plot3d/Plot3DWindow.py20
-rw-r--r--silx/gui/plot3d/SFViewParamTree.py142
-rw-r--r--silx/gui/plot3d/ScalarFieldView.py270
-rw-r--r--silx/gui/plot3d/__init__.py7
-rw-r--r--silx/gui/plot3d/actions/Plot3DAction.py69
-rw-r--r--silx/gui/plot3d/actions/__init__.py33
-rw-r--r--silx/gui/plot3d/actions/io.py (renamed from silx/gui/plot3d/Plot3DActions.py)44
-rw-r--r--silx/gui/plot3d/actions/mode.py126
-rw-r--r--silx/gui/plot3d/scene/axes.py19
-rw-r--r--silx/gui/plot3d/scene/function.py128
-rw-r--r--silx/gui/plot3d/scene/interaction.py29
-rw-r--r--silx/gui/plot3d/scene/primitives.py7
-rw-r--r--silx/gui/plot3d/scene/viewport.py15
-rw-r--r--silx/gui/plot3d/scene/window.py11
-rw-r--r--silx/gui/plot3d/setup.py2
-rw-r--r--silx/gui/plot3d/test/__init__.py4
-rw-r--r--silx/gui/plot3d/test/testGL.py84
-rw-r--r--silx/gui/plot3d/test/testScalarFieldView.py114
-rw-r--r--silx/gui/plot3d/tools/ViewpointTools.py (renamed from silx/gui/plot3d/ViewpointToolBar.py)29
-rw-r--r--silx/gui/plot3d/tools/__init__.py32
-rw-r--r--silx/gui/plot3d/tools/toolbars.py (renamed from silx/gui/plot3d/Plot3DToolBar.py)85
-rw-r--r--silx/gui/setup.py2
-rw-r--r--silx/gui/test/test_icons.py56
-rw-r--r--silx/gui/test/utils.py36
-rw-r--r--silx/gui/widgets/FloatEdit.py65
-rw-r--r--silx/gui/widgets/FrameBrowser.py4
-rw-r--r--silx/gui/widgets/PeriodicTable.py6
-rw-r--r--silx/gui/widgets/PrintGeometryDialog.py222
-rw-r--r--silx/gui/widgets/PrintPreview.py704
-rw-r--r--silx/gui/widgets/TableWidget.py162
-rw-r--r--silx/gui/widgets/ThreadPoolPushButton.py2
-rw-r--r--silx/gui/widgets/WaitingPushButton.py2
-rw-r--r--silx/gui/widgets/test/__init__.py4
-rw-r--r--silx/gui/widgets/test/test_printpreview.py74
-rw-r--r--silx/image/backprojection.py25
-rw-r--r--silx/image/bilinear.c9557
-rw-r--r--silx/image/bilinear.pyx2
-rw-r--r--silx/image/phantomgenerator.py160
-rw-r--r--silx/image/projection.py25
-rw-r--r--silx/image/reconstruction.py25
-rw-r--r--silx/image/shapes.c9927
-rw-r--r--silx/image/sift.py26
-rw-r--r--silx/image/test/__init__.py2
-rw-r--r--silx/image/test/test_bilinear.py4
-rw-r--r--silx/image/test/test_shapes.py4
-rw-r--r--silx/image/test/test_tomography.py66
-rw-r--r--silx/image/tomography.py159
-rw-r--r--silx/io/__init__.py14
-rw-r--r--silx/io/commonh5.py991
-rw-r--r--silx/io/convert.py302
-rw-r--r--silx/io/dictdump.py158
-rw-r--r--silx/io/fabioh5.py569
-rw-r--r--silx/io/nxdata.py114
-rw-r--r--silx/io/rawh5.py71
-rw-r--r--silx/io/setup.py4
-rw-r--r--silx/io/specfile.c (renamed from silx/io/specfile/specfile.c)14347
-rw-r--r--silx/io/specfile.pyx (renamed from silx/io/specfile/specfile.pyx)191
-rw-r--r--silx/io/specfile/include/Lists.h31
-rw-r--r--silx/io/specfile/include/SpecFile.h32
-rw-r--r--silx/io/specfile/include/SpecFileP.h31
-rw-r--r--silx/io/specfile/include/locale_management.h27
-rw-r--r--silx/io/specfile/src/locale_management.c31
-rw-r--r--silx/io/specfile/src/sfdata.c31
-rw-r--r--silx/io/specfile/src/sfheader.c31
-rw-r--r--silx/io/specfile/src/sfindex.c31
-rw-r--r--silx/io/specfile/src/sfinit.c31
-rw-r--r--silx/io/specfile/src/sflabel.c31
-rw-r--r--silx/io/specfile/src/sflists.c31
-rw-r--r--silx/io/specfile/src/sfmca.c31
-rw-r--r--silx/io/specfile/src/sftools.c31
-rw-r--r--silx/io/specfile/src/sfwrite.c31
-rw-r--r--silx/io/specfile_wrapper.pxd (renamed from silx/io/specfile/specfile_wrapper.pxd)0
-rw-r--r--silx/io/spech5.py1573
-rw-r--r--silx/io/spectoh5.py301
-rw-r--r--silx/io/test/__init__.py6
-rw-r--r--silx/io/test/test_commonh5.py306
-rw-r--r--silx/io/test/test_fabioh5.py93
-rw-r--r--silx/io/test/test_nxdata.py7
-rw-r--r--silx/io/test/test_rawh5.py96
-rw-r--r--silx/io/test/test_specfile.py9
-rw-r--r--silx/io/test/test_specfilewrapper.py3
-rw-r--r--silx/io/test/test_spech5.py123
-rw-r--r--silx/io/test/test_spectoh5.py23
-rw-r--r--silx/io/test/test_utils.py186
-rw-r--r--silx/io/utils.py141
-rw-r--r--silx/math/chistogramnd.c (renamed from silx/math/histogramnd/chistogramnd.c)17088
-rw-r--r--silx/math/chistogramnd.pyx (renamed from silx/math/histogramnd/chistogramnd.pyx)28
-rw-r--r--silx/math/chistogramnd_lut.c (renamed from silx/math/histogramnd/chistogramnd_lut.c)26584
-rw-r--r--silx/math/chistogramnd_lut.pyx (renamed from silx/math/histogramnd/chistogramnd_lut.pyx)23
-rw-r--r--silx/math/combo.c (renamed from silx/math/combo/combo.c)23088
-rw-r--r--silx/math/combo.pyx (renamed from silx/math/combo/combo.pyx)106
-rw-r--r--silx/math/fit/filters.c (renamed from silx/math/fit/filters/filters.c)11128
-rw-r--r--silx/math/fit/filters.pyx (renamed from silx/math/fit/filters/filters.pyx)3
-rw-r--r--silx/math/fit/filters_wrapper.pxd (renamed from silx/math/fit/filters/filters_wrapper.pxd)0
-rw-r--r--silx/math/fit/fittheories.py3
-rw-r--r--silx/math/fit/functions.c (renamed from silx/math/fit/functions/functions.c)16233
-rw-r--r--silx/math/fit/functions.pyx (renamed from silx/math/fit/functions/functions.pyx)41
-rw-r--r--silx/math/fit/functions_wrapper.pxd (renamed from silx/math/fit/functions/functions_wrapper.pxd)0
-rw-r--r--silx/math/fit/leastsq.py3
-rw-r--r--silx/math/fit/peaks.c (renamed from silx/math/fit/peaks/peaks.c)9310
-rw-r--r--silx/math/fit/peaks.pyx (renamed from silx/math/fit/peaks/peaks.pyx)5
-rw-r--r--silx/math/fit/peaks_wrapper.pxd (renamed from silx/math/fit/peaks/peaks_wrapper.pxd)0
-rw-r--r--silx/math/fit/setup.py41
-rw-r--r--silx/math/fit/test/test_fit.py5
-rw-r--r--silx/math/histogram.py6
-rw-r--r--silx/math/histogramnd_c.pxd (renamed from silx/math/histogramnd/histogramnd_c.pxd)2
-rw-r--r--silx/math/include/isnan.h (renamed from silx/math/combo/isnan.h)12
-rw-r--r--silx/math/marchingcubes.cpp (renamed from silx/math/marchingcubes/marchingcubes.cpp)12019
-rw-r--r--silx/math/marchingcubes.pyx (renamed from silx/math/marchingcubes/marchingcubes.pyx)4
-rw-r--r--silx/math/mc.pxd (renamed from silx/math/marchingcubes/mc.pxd)0
-rw-r--r--silx/math/medianfilter/__init__.py2
-rw-r--r--silx/math/medianfilter/include/median_filter.hpp154
-rw-r--r--silx/math/medianfilter/median_filter.pxd3
-rw-r--r--silx/math/medianfilter/medianfilter.cpp13266
-rw-r--r--silx/math/medianfilter/medianfilter.pyx145
-rw-r--r--silx/math/medianfilter/test/test_medianfilter.py446
-rw-r--r--silx/math/setup.py33
-rw-r--r--silx/math/test/benchmark_combo.py4
-rw-r--r--silx/math/test/test_HistogramndLut_nominal.py15
-rw-r--r--silx/math/test/test_combo.py141
-rw-r--r--silx/math/test/test_histogramnd_error.py25
-rw-r--r--silx/math/test/test_histogramnd_nominal.py21
-rw-r--r--silx/opencl/__init__.py2
-rw-r--r--silx/opencl/backprojection.py488
-rw-r--r--silx/opencl/common.py29
-rw-r--r--silx/opencl/linalg.py218
-rw-r--r--silx/opencl/medfilt.py8
-rw-r--r--silx/opencl/processing.py61
-rw-r--r--silx/opencl/projection.py419
-rw-r--r--silx/opencl/reconstruction.py381
-rw-r--r--silx/opencl/setup.py9
-rw-r--r--silx/opencl/test/__init__.py21
-rw-r--r--silx/opencl/test/test_array_utils.py161
-rw-r--r--silx/opencl/test/test_backprojection.py165
-rw-r--r--silx/opencl/test/test_linalg.py215
-rw-r--r--silx/opencl/test/test_projection.py139
-rw-r--r--silx/opencl/utils.py71
-rw-r--r--silx/resources/__init__.py231
-rw-r--r--silx/resources/gui/colormaps/inferno.npybin0 -> 3152 bytes
-rw-r--r--silx/resources/gui/colormaps/magma.npybin0 -> 3152 bytes
-rw-r--r--silx/resources/gui/colormaps/plasma.npybin0 -> 3152 bytes
-rw-r--r--silx/resources/gui/colormaps/viridis.npybin0 -> 3152 bytes
-rw-r--r--silx/resources/gui/icons/3d-plane-normal-x.svg23
-rw-r--r--silx/resources/gui/icons/3d-plane-normal-y.svg23
-rw-r--r--silx/resources/gui/icons/3d-plane-normal-z.svg27
-rw-r--r--silx/resources/gui/icons/3d-plane-pan.pngbin0 -> 1428 bytes
-rw-r--r--silx/resources/gui/icons/3d-plane-pan.svg14
-rw-r--r--silx/resources/gui/icons/3d-plane.svg19
-rw-r--r--silx/resources/gui/icons/arrow-keys.svg2
-rw-r--r--silx/resources/gui/icons/axis.pngbin0 -> 1740 bytes
-rw-r--r--silx/resources/gui/icons/axis.svg2
-rw-r--r--silx/resources/gui/icons/camera.svg23
-rw-r--r--silx/resources/gui/icons/close.svg70
-rw-r--r--silx/resources/gui/icons/colorbar.pngbin0 -> 657 bytes
-rw-r--r--silx/resources/gui/icons/colorbar.svg3
-rw-r--r--silx/resources/gui/icons/crosshair.svg42
-rw-r--r--silx/resources/gui/icons/cube-back.svg23
-rw-r--r--silx/resources/gui/icons/cube-bottom.svg23
-rw-r--r--silx/resources/gui/icons/cube-front.svg23
-rw-r--r--silx/resources/gui/icons/cube-left.svg23
-rw-r--r--silx/resources/gui/icons/cube-right.svg23
-rw-r--r--silx/resources/gui/icons/cube-top.svg23
-rw-r--r--silx/resources/gui/icons/cube.svg25
-rw-r--r--silx/resources/gui/icons/document-open.svg128
-rw-r--r--silx/resources/gui/icons/document-print.svg14
-rw-r--r--silx/resources/gui/icons/document-save.svg38
-rw-r--r--silx/resources/gui/icons/draw-brush.svg8
-rw-r--r--silx/resources/gui/icons/draw-pencil.svg41
-rw-r--r--silx/resources/gui/icons/draw-rubber.svg45
-rw-r--r--silx/resources/gui/icons/edit-copy.svg77
-rw-r--r--silx/resources/gui/icons/first.svg37
-rw-r--r--silx/resources/gui/icons/folder.svg122
-rw-r--r--silx/resources/gui/icons/image-mask.svg27
-rw-r--r--silx/resources/gui/icons/image-select-box.svg123
-rw-r--r--silx/resources/gui/icons/image-select-brush.svg125
-rw-r--r--silx/resources/gui/icons/image-select-erase-rubber.svg8
-rw-r--r--silx/resources/gui/icons/image-select-erase.svg4
-rw-r--r--silx/resources/gui/icons/item-0dim.svg2
-rw-r--r--silx/resources/gui/icons/item-1dim.svg2
-rw-r--r--silx/resources/gui/icons/item-2dim.svg2
-rw-r--r--silx/resources/gui/icons/item-3dim.svg10
-rw-r--r--silx/resources/gui/icons/item-ndim.svg46
-rw-r--r--silx/resources/gui/icons/item-none.pngbin0 -> 637 bytes
-rw-r--r--silx/resources/gui/icons/item-none.svg5
-rw-r--r--silx/resources/gui/icons/item-object.svg22
-rw-r--r--silx/resources/gui/icons/last.svg37
-rw-r--r--silx/resources/gui/icons/math-amplitude.pngbin0 -> 526 bytes
-rw-r--r--silx/resources/gui/icons/math-amplitude.svg3
-rw-r--r--silx/resources/gui/icons/math-derive.svg2
-rw-r--r--silx/resources/gui/icons/math-fit.svg35
-rw-r--r--silx/resources/gui/icons/math-imaginary.pngbin0 -> 630 bytes
-rw-r--r--silx/resources/gui/icons/math-imaginary.svg3
-rw-r--r--silx/resources/gui/icons/math-normalize.svg46
-rw-r--r--silx/resources/gui/icons/math-peak-reset.svg57
-rw-r--r--silx/resources/gui/icons/math-peak-search.svg58
-rw-r--r--silx/resources/gui/icons/math-peak.svg41
-rw-r--r--silx/resources/gui/icons/math-phase-color-log.pngbin0 -> 2256 bytes
-rw-r--r--silx/resources/gui/icons/math-phase-color-log.svg3
-rw-r--r--silx/resources/gui/icons/math-phase-color.pngbin0 -> 2127 bytes
-rw-r--r--silx/resources/gui/icons/math-phase-color.svg3
-rw-r--r--silx/resources/gui/icons/math-phase.pngbin0 -> 515 bytes
-rw-r--r--silx/resources/gui/icons/math-phase.svg3
-rw-r--r--silx/resources/gui/icons/math-real.pngbin0 -> 749 bytes
-rw-r--r--silx/resources/gui/icons/math-real.svg3
-rw-r--r--silx/resources/gui/icons/math-sigma.svg93
-rw-r--r--silx/resources/gui/icons/math-smooth.svg29
-rw-r--r--silx/resources/gui/icons/math-substract.svg56
-rw-r--r--silx/resources/gui/icons/math-swap-sign.svg59
-rw-r--r--silx/resources/gui/icons/math-ymin-to-zero.svg42
-rw-r--r--silx/resources/gui/icons/median-filter.svg76
-rw-r--r--silx/resources/gui/icons/next.svg33
-rw-r--r--silx/resources/gui/icons/normal.svg35
-rw-r--r--silx/resources/gui/icons/pan.pngbin0 -> 526 bytes
-rw-r--r--silx/resources/gui/icons/pan.svg9
-rw-r--r--silx/resources/gui/icons/pixel-intensities.svg39
-rw-r--r--silx/resources/gui/icons/plot-roi-above.svg2
-rw-r--r--silx/resources/gui/icons/plot-roi-below.svg2
-rw-r--r--silx/resources/gui/icons/plot-roi-between.svg2
-rw-r--r--silx/resources/gui/icons/plot-roi-reset.svg69
-rw-r--r--silx/resources/gui/icons/plot-roi.svg54
-rw-r--r--silx/resources/gui/icons/plot-toggle-points.svg54
-rw-r--r--silx/resources/gui/icons/plot-widget.svg2
-rw-r--r--silx/resources/gui/icons/plot-window-image.svg2
-rw-r--r--silx/resources/gui/icons/plot-window.svg2
-rw-r--r--silx/resources/gui/icons/plot-xauto.svg41
-rw-r--r--silx/resources/gui/icons/plot-xlog.svg47
-rw-r--r--silx/resources/gui/icons/plot-yauto.svg36
-rw-r--r--silx/resources/gui/icons/plot-ydown.svg32
-rw-r--r--silx/resources/gui/icons/plot-ylog.svg42
-rw-r--r--silx/resources/gui/icons/plot-yup.svg41
-rw-r--r--silx/resources/gui/icons/previous.svg33
-rw-r--r--silx/resources/gui/icons/process-working/00.png (renamed from silx/resources/gui/icons/animated/process-working-00.png)bin778 -> 778 bytes
-rw-r--r--silx/resources/gui/icons/process-working/01.png (renamed from silx/resources/gui/icons/animated/process-working-01.png)bin789 -> 789 bytes
-rw-r--r--silx/resources/gui/icons/process-working/02.png (renamed from silx/resources/gui/icons/animated/process-working-02.png)bin785 -> 785 bytes
-rw-r--r--silx/resources/gui/icons/process-working/03.png (renamed from silx/resources/gui/icons/animated/process-working-03.png)bin785 -> 785 bytes
-rw-r--r--silx/resources/gui/icons/process-working/04.png (renamed from silx/resources/gui/icons/animated/process-working-04.png)bin766 -> 766 bytes
-rw-r--r--silx/resources/gui/icons/process-working/05.png (renamed from silx/resources/gui/icons/animated/process-working-05.png)bin777 -> 777 bytes
-rw-r--r--silx/resources/gui/icons/process-working/06.png (renamed from silx/resources/gui/icons/animated/process-working-06.png)bin784 -> 784 bytes
-rw-r--r--silx/resources/gui/icons/process-working/07.png (renamed from silx/resources/gui/icons/animated/process-working-07.png)bin783 -> 783 bytes
-rw-r--r--silx/resources/gui/icons/process-working/08.png (renamed from silx/resources/gui/icons/animated/process-working-08.png)bin762 -> 762 bytes
-rw-r--r--silx/resources/gui/icons/process-working/09.png (renamed from silx/resources/gui/icons/animated/process-working-09.png)bin781 -> 781 bytes
-rw-r--r--silx/resources/gui/icons/process-working/10.png (renamed from silx/resources/gui/icons/animated/process-working-10.png)bin771 -> 771 bytes
-rw-r--r--silx/resources/gui/icons/process-working/11.png (renamed from silx/resources/gui/icons/animated/process-working-11.png)bin768 -> 768 bytes
-rw-r--r--silx/resources/gui/icons/process-working/12.png (renamed from silx/resources/gui/icons/animated/process-working-12.png)bin759 -> 759 bytes
-rw-r--r--silx/resources/gui/icons/process-working/13.png (renamed from silx/resources/gui/icons/animated/process-working-13.png)bin767 -> 767 bytes
-rw-r--r--silx/resources/gui/icons/process-working/14.png (renamed from silx/resources/gui/icons/animated/process-working-14.png)bin778 -> 778 bytes
-rw-r--r--silx/resources/gui/icons/process-working/15.png (renamed from silx/resources/gui/icons/animated/process-working-15.png)bin760 -> 760 bytes
-rw-r--r--silx/resources/gui/icons/process-working/16.png (renamed from silx/resources/gui/icons/animated/process-working-16.png)bin754 -> 754 bytes
-rw-r--r--silx/resources/gui/icons/process-working/17.png (renamed from silx/resources/gui/icons/animated/process-working-17.png)bin782 -> 782 bytes
-rw-r--r--silx/resources/gui/icons/process-working/18.png (renamed from silx/resources/gui/icons/animated/process-working-18.png)bin775 -> 775 bytes
-rw-r--r--silx/resources/gui/icons/process-working/19.png (renamed from silx/resources/gui/icons/animated/process-working-19.png)bin764 -> 764 bytes
-rw-r--r--silx/resources/gui/icons/process-working/20.png (renamed from silx/resources/gui/icons/animated/process-working-20.png)bin764 -> 764 bytes
-rw-r--r--silx/resources/gui/icons/process-working/21.png (renamed from silx/resources/gui/icons/animated/process-working-21.png)bin772 -> 772 bytes
-rw-r--r--silx/resources/gui/icons/process-working/22.png (renamed from silx/resources/gui/icons/animated/process-working-22.png)bin769 -> 769 bytes
-rw-r--r--silx/resources/gui/icons/process-working/23.png (renamed from silx/resources/gui/icons/animated/process-working-23.png)bin773 -> 773 bytes
-rw-r--r--silx/resources/gui/icons/process-working/24.png (renamed from silx/resources/gui/icons/animated/process-working-24.png)bin757 -> 757 bytes
-rw-r--r--silx/resources/gui/icons/process-working/25.png (renamed from silx/resources/gui/icons/animated/process-working-25.png)bin759 -> 759 bytes
-rw-r--r--silx/resources/gui/icons/process-working/26.png (renamed from silx/resources/gui/icons/animated/process-working-26.png)bin774 -> 774 bytes
-rw-r--r--silx/resources/gui/icons/process-working/27.png (renamed from silx/resources/gui/icons/animated/process-working-27.png)bin766 -> 766 bytes
-rw-r--r--silx/resources/gui/icons/process-working/28.png (renamed from silx/resources/gui/icons/animated/process-working-28.png)bin760 -> 760 bytes
-rw-r--r--silx/resources/gui/icons/process-working/29.png (renamed from silx/resources/gui/icons/animated/process-working-29.png)bin777 -> 777 bytes
-rw-r--r--silx/resources/gui/icons/process-working/30.png (renamed from silx/resources/gui/icons/animated/process-working-30.png)bin775 -> 775 bytes
-rw-r--r--silx/resources/gui/icons/profile-clear.svg45
-rw-r--r--silx/resources/gui/icons/profile1D.svg23
-rw-r--r--silx/resources/gui/icons/profile2D.svg29
-rw-r--r--silx/resources/gui/icons/remove.svg64
-rw-r--r--silx/resources/gui/icons/rotate-3d.pngbin0 -> 760 bytes
-rw-r--r--silx/resources/gui/icons/rotate-3d.svg7
-rw-r--r--silx/resources/gui/icons/rudder.svg26
-rw-r--r--silx/resources/gui/icons/selected.svg41
-rw-r--r--silx/resources/gui/icons/shape-polygon.svg24
-rw-r--r--silx/resources/gui/icons/shape-rectangle.svg27
-rw-r--r--silx/resources/gui/icons/shape-square.svg27
-rw-r--r--silx/resources/gui/icons/shape-vertical.svg31
-rw-r--r--silx/resources/gui/icons/silx.svg2
-rw-r--r--silx/resources/gui/icons/sliders-off.svg120
-rw-r--r--silx/resources/gui/icons/sliders-on.svg108
-rw-r--r--silx/resources/gui/icons/spec.svg58
-rw-r--r--silx/resources/gui/icons/test-png.pngbin233 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/test-svg.svg15
-rw-r--r--silx/resources/gui/icons/view-1d.svg21
-rw-r--r--silx/resources/gui/icons/view-2d-stack.svg17
-rw-r--r--silx/resources/gui/icons/view-2d.svg13
-rw-r--r--silx/resources/gui/icons/view-3d.svg21
-rw-r--r--silx/resources/gui/icons/view-fullscreen.svg72
-rw-r--r--silx/resources/gui/icons/view-hdf5.svg15
-rw-r--r--silx/resources/gui/icons/view-nexus.svg72
-rw-r--r--silx/resources/gui/icons/view-nofullscreen.svg97
-rw-r--r--silx/resources/gui/icons/view-raw.svg29
-rw-r--r--silx/resources/gui/icons/view-refresh.svg36
-rw-r--r--silx/resources/gui/icons/view-text.svg25
-rw-r--r--silx/resources/gui/icons/window-new.svg41
-rw-r--r--silx/resources/gui/icons/zoom-back.pngbin0 -> 1432 bytes
-rw-r--r--silx/resources/gui/icons/zoom-back.svg2
-rw-r--r--silx/resources/gui/icons/zoom-in.svg109
-rw-r--r--silx/resources/gui/icons/zoom-original.svg109
-rw-r--r--silx/resources/gui/icons/zoom-out.svg103
-rw-r--r--silx/resources/gui/icons/zoom.svg97
-rw-r--r--silx/resources/gui/logo/silx.pngbin0 -> 21257 bytes
-rw-r--r--silx/resources/gui/logo/silx.svg118
-rw-r--r--silx/resources/opencl/addition.cl12
-rw-r--r--silx/resources/opencl/array_utils.cl33
-rw-r--r--silx/resources/opencl/backproj.cl485
-rw-r--r--silx/resources/opencl/backproj_helper.cl68
-rw-r--r--silx/resources/opencl/linalg.cl89
-rw-r--r--silx/resources/opencl/proj.cl345
-rw-r--r--silx/sx/__init__.py4
-rw-r--r--silx/sx/_plot.py27
-rw-r--r--silx/test/__init__.py6
-rw-r--r--silx/test/test_resources.py212
-rw-r--r--silx/test/utils.py10
-rw-r--r--silx/third_party/_local/enum.py877
-rw-r--r--silx/third_party/enum.py51
-rw-r--r--silx/utils/decorators.py71
-rw-r--r--silx/utils/deprecation.py117
-rw-r--r--silx/utils/proxy.py204
-rw-r--r--silx/utils/test/__init__.py22
-rw-r--r--silx/utils/test/test_deprecation.py107
-rw-r--r--silx/utils/test/test_proxy.py295
-rw-r--r--version.py16
582 files changed, 134116 insertions, 76091 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 2e98df3..b54c4fa 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,62 @@
Change Log
==========
+0.6.0: 2017/10/02
+-----------------
+
+ * OpenCl. Tomography. Implement a filtered back projection.
+ * Add a *PrintPreview* widget and a *PrintPreviewToolButton* for *PlotWidget*.
+ * Plot:
+
+ * Add a context menu on right click.
+ * Add a *ComplexImageView* widget.
+ * Merged abstract *Plot* class with *PlotWidget* class.
+ * Make colormap an object with signals (*sigChanged*)
+ * Add a colorbar widget *silx.gui.plot.ColorBar*.
+ * Make axis an object, allow axis synchronization between plots,
+ allow adding constraints on axes limits.
+ * Refactor plot actions, new sub-package *silx.gui.plot.actions*.
+ * Add signals on *PlotWidget* items notifying updates.
+ * Mask. Support loading of TIFF images.
+
+ * Plot3d:
+
+ * Rework toolbar and interaction to use only the left mouse button.
+ * Support any colormap.
+
+ * Hdf5TreeView:
+
+ * Add an API to select a single tree node item (*setSelectedH5Node*)
+ * Better support and display of types.
+ * New column for displaying the kind of links.
+ * Broken nodes are now selectable.
+
+ * StackView. Add a *setTitleCallback* method.
+ * Median filter. Add new modes (*reflect, mirror, shrink*) in addition to *nearest*.
+
+ * IO:
+
+ * Rename module *spectoh5* to *convert*. Add support for conversion of *fabio* formats.
+ * Support NPZ format.
+ * Support opening an URI (*silx.io.open(filename::path)*).
+ * *Group* methods *.keys*, *.value* and *.items* now return lists in Python 2
+ and iterators in Python 3.
+
+ * Image. Add tomography utils: *phantomgenerator* to produce Shepp-Logan phantom, function to compute center of rotation (*calc_center_corr*, *calc_center_centroid*) and rescale the intensity of an image (*rescale_intensity*).
+
+ * Commands:
+
+ * *silx view*:
+
+ * Add command line option *--use-opengl-plot*.
+ * Add command line option *--debug*, to print dataset reading errors.
+ * Support opening URI (*silx view filename::path*).
+
+ * *silx convert*. New command line application to convert supported data files to HDF5.
+
+ * Enable usage of *silx.resources* for other projects.
+ * The *silx* license is now fully MIT.
+
0.5.0: 2017/05/12
-----------------
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..acebc8d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+The silx toolkit is a software library and one of its goals is not to impose any license to the end user.
+
+Silx follows the permissive MIT license although it may include contributions following other licenses not interfering with the previous goal. Detailed information can be found in the copyright file.
+
+Silx uses the Qt library for its graphical user interfaces.
+A word of caution is to be provided.
+If users develop and distribute software using modules accessing Qt by means of Riverbank Computing Qt bindings PyQt4 or PyQt5, those users will be conditioned by the license of their PyQt4/5 software (GPL or commercial).
+If the end user does not own a commercial license of PyQt4 or PyQt5 and wishes to be free of any distribution condition, (s)he should be able to use PySide because it uses the LGPL license.
+
+The MIT license follows:
+
+Copyright (c) European Synchrotron Radiation Facility (ESRF)
+
+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.
diff --git a/MANIFEST.in b/MANIFEST.in
index 4334bba..0a8a964 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,13 +1,18 @@
include README.rst
include CHANGELOG.rst
include copyright
+include LICENSE
include MANIFEST.in
include run_tests.py
include version.py
include stdeb.cfg
+include build-deb.sh
+include requirements.txt
+include requirements-dev.txt
recursive-include silx *.pyx *.pxd *.pxi
recursive-include silx *.h *.c *.hpp *.cpp
recursive-include doc/source *.py *.rst *.png *.ico
recursive-include qtdesigner_plugins *.py *.rst
recursive-include silx/resources *
recursive-include examples *
+recursive-include package *
diff --git a/PKG-INFO b/PKG-INFO
index 7a6c11c..7649c9a 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: silx
-Version: 0.5.0
+Version: 0.6.0
Summary: Software library for X-Ray data analysis
Home-page: https://github.com/silx-kit/silx
Author: data analysis unit
@@ -10,20 +10,30 @@ Description:
silx toolkit
============
- 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 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 :
+ The current version provides:
- * reading `HDF5 <https://www.hdfgroup.org/HDF5/>`_ file format (with support of `SPEC <https://certif.com/spec.html>`_ file format)
+ * reading `HDF5 <https://www.hdfgroup.org/HDF5/>`_ file format (with support of
+ `SPEC <https://certif.com/spec.html>`_ file format and
+ `FabIO <http://www.silx.org/doc/fabio/dev/getting_started.html#list-of-file-formats-that-fabio-can-read-and-write>`_
+ images)
* histogramming
* fitting
- * 1D and 2D visualization using multiple backends (matplotlib or OpenGL)
- * image plot widget with a set of associated tools (See `changelog file <https://github.com/silx-kit/silx/blob/master/CHANGELOG.rst>`_).
- * 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.
+ * 1D and 2D visualization widgets using multiple backends (matplotlib or OpenGL)
+ * an OpenGL-based widget to display 3D scalar field with isosurface and cutting plane
+ * an image plot widget with a set of associated tools
+ * a unified browser for HDF5, SPEC and image file formats supporting inspection and
+ visualization of n-dimensional datasets.
+ * a unified viewer (*silx view filename*) for HDF5, SPEC and image file formats
+ * a unified converter to HDF5 format (*silx convert filename*)
+ * median filters on images (C and OpenCL implementations)
* image alignement (sift - OpenCL implementation)
+ * filtered backprojection and forward projection for tomography
Installation
------------
@@ -31,21 +41,36 @@ Description:
To install silx, run::
pip install silx
+
+ Or with Anaconda on Linux and MacOS::
+
+ conda install silx -c conda-forge
To install silx locally, run::
pip install silx --user
- On Linux, to install silx with pip, you must install numpy first. Unofficial packages for different distributions are available :
+ Unofficial packages for different distributions are available :
- Unofficial Debian8 packages are available at http://www.silx.org/pub/debian/
- CentOS 7 rpm packages are provided by Max IV at the following url: http://pubrepo.maxiv.lu.se/rpm/el7/x86_64/
- Fedora 23 rpm packages are provided by Max IV at http://pubrepo.maxiv.lu.se/rpm/fc23/x86_64/
- Arch Linux (AUR) packages are also available: https://aur.archlinux.org/packages/python-silx
- On Windows, pre-compiled binaries (aka Python wheels) are available for Python 2.7, 3.5 and 3.6.
+ Beside this, we provide a certain number of wheels (pre-compiled binary packages) to be installed
+ onto a pre-existing Python installation:
+
+ - On Windows, binary wheels are available for Python 2.7, 3.5 and 3.6.
+ - On MacOS, binary wheels are available for Python 2.7, 3.4, 3.5 and 3.6.
+ - On Linux, manylinux1 binary wheels are available for Python 2.7, 3.4, 3.5 and 3.6.
+
+ Those builds are made from "up-date" systems at the time of the release, i.e. they use
+ the latest stable version of numpy (and cython).
+ Hence your system should use a fairly recent version of numpy to be compatible with silx.
+ This can be achieved simply by::
+
+ pip install numpy --upgrade
- On Mac OS X, pre-compiled binaries (aka Python wheels) are available for Python 2.7.
The latest development version can be obtained from the git repository::
@@ -65,19 +90,28 @@ Description:
* `matplotlib <http://matplotlib.org/>`_ for the silx.gui.plot package
* `PyOpenGL <http://pyopengl.sourceforge.net/>`_ for the silx.gui.plot3d package
- Most modules and functions dealing with `HDF5 <https://www.hdfgroup.org/HDF5/>`_ input/output depend on the following extra package:
+ Most modules and functions dealing with `HDF5 <https://www.hdfgroup.org/HDF5/>`_ input/output depend on:
+
* `h5py <http://www.h5py.org/>`_
- * `ipython <https://ipython.org/>`_ and `qtconsole <https://pypi.python.org/pypi/qtconsole>`_ is required by silx.gui.console.py
+ Parallel algorithms depend on:
+
+ * `PyOpenCL <https://documen.tician.de/pyopencl/>`_
+
+ The console widgets depend on:
+
+ * `ipython <https://ipython.org/>`_
+ * `qtconsole <https://pypi.python.org/pypi/qtconsole>`_
+
Supported platforms: Linux, Windows, Mac OS X.
Documentation
-------------
- Documentation of releases is available at https://pythonhosted.org/silx/
+ Documentation of latest release is available at http://www.silx.org/doc/silx/latest/
- Latest documentation (nightly build) is available at http://www.silx.org/doc/silx/
+ Documentation of previous releases and nightly build is available at http://www.silx.org/doc/silx/
To build the documentation from the source (requires `Sphinx <http://www.sphinx-doc.org>`_), run::
@@ -101,21 +135,28 @@ Description:
Examples
--------
- Some examples are available in the source code repository. For example::
-
- python examples/{exampleName.py}
+ Some examples of sample code using silx are provided with the
+ `silx documentation <http://www.silx.org/doc/silx/dev/sample_code/index.html>`_.
License
-------
- The source code of silx is licensed under the MIT and LGPL licenses.
- See the `copyright file <https://github.com/silx-kit/silx/blob/master/copyright>`_ for details.
+ The source code of silx is licensed under the MIT license.
+ See the `LICENSE <https://github.com/silx-kit/silx/blob/master/LICENSE>`_ and `copyright <https://github.com/silx-kit/silx/blob/master/copyright>`_ files for details.
+
+ Citation
+ --------
+
+ silx releases can be cited by their DOI on Zenodo: |DOI:10.5281/zenodo.1000472|
.. |Travis Status| image:: https://travis-ci.org/silx-kit/silx.svg?branch=master
:target: https://travis-ci.org/silx-kit/silx
.. |Appveyor Status| image:: https://ci.appveyor.com/api/projects/status/qgox9ei0wxwfagrb/branch/master?svg=true
:target: https://ci.appveyor.com/project/ESRF/silx
+ .. |DOI:10.5281/zenodo.1000472| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.1000472.svg
+ :target: https://doi.org/10.5281/zenodo.1000472
+
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
@@ -126,7 +167,6 @@ Classifier: Environment :: X11 Applications :: Qt
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
-Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)
Classifier: Natural Language :: English
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
@@ -135,6 +175,7 @@ Classifier: Programming Language :: Cython
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Scientific/Engineering :: Physics
Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/README.rst b/README.rst
index 38ce368..5983c46 100644
--- a/README.rst
+++ b/README.rst
@@ -2,20 +2,30 @@
silx toolkit
============
-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 <https://www.hdfgroup.org/HDF5/>`_ file format (with support of `SPEC <https://certif.com/spec.html>`_ file format)
+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 <https://www.hdfgroup.org/HDF5/>`_ file format (with support of
+ `SPEC <https://certif.com/spec.html>`_ file format and
+ `FabIO <http://www.silx.org/doc/fabio/dev/getting_started.html#list-of-file-formats-that-fabio-can-read-and-write>`_
+ images)
* histogramming
* fitting
-* 1D and 2D visualization using multiple backends (matplotlib or OpenGL)
-* image plot widget with a set of associated tools (See `changelog file <https://github.com/silx-kit/silx/blob/master/CHANGELOG.rst>`_).
-* 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.
+* 1D and 2D visualization widgets using multiple backends (matplotlib or OpenGL)
+* an OpenGL-based widget to display 3D scalar field with isosurface and cutting plane
+* an image plot widget with a set of associated tools
+* a unified browser for HDF5, SPEC and image file formats supporting inspection and
+ visualization of n-dimensional datasets.
+* a unified viewer (*silx view filename*) for HDF5, SPEC and image file formats
+* a unified converter to HDF5 format (*silx convert filename*)
+* median filters on images (C and OpenCL implementations)
* image alignement (sift - OpenCL implementation)
+* filtered backprojection and forward projection for tomography
Installation
------------
@@ -23,21 +33,36 @@ Installation
To install silx, run::
pip install silx
+
+Or with Anaconda on Linux and MacOS::
+
+ conda install silx -c conda-forge
To install silx locally, run::
pip install silx --user
-On Linux, to install silx with pip, you must install numpy first. Unofficial packages for different distributions are available :
+Unofficial packages for different distributions are available :
- Unofficial Debian8 packages are available at http://www.silx.org/pub/debian/
- CentOS 7 rpm packages are provided by Max IV at the following url: http://pubrepo.maxiv.lu.se/rpm/el7/x86_64/
- Fedora 23 rpm packages are provided by Max IV at http://pubrepo.maxiv.lu.se/rpm/fc23/x86_64/
- Arch Linux (AUR) packages are also available: https://aur.archlinux.org/packages/python-silx
-On Windows, pre-compiled binaries (aka Python wheels) are available for Python 2.7, 3.5 and 3.6.
+Beside this, we provide a certain number of wheels (pre-compiled binary packages) to be installed
+onto a pre-existing Python installation:
+
+- On Windows, binary wheels are available for Python 2.7, 3.5 and 3.6.
+- On MacOS, binary wheels are available for Python 2.7, 3.4, 3.5 and 3.6.
+- On Linux, manylinux1 binary wheels are available for Python 2.7, 3.4, 3.5 and 3.6.
+
+Those builds are made from "up-date" systems at the time of the release, i.e. they use
+the latest stable version of numpy (and cython).
+Hence your system should use a fairly recent version of numpy to be compatible with silx.
+This can be achieved simply by::
+
+ pip install numpy --upgrade
-On Mac OS X, pre-compiled binaries (aka Python wheels) are available for Python 2.7.
The latest development version can be obtained from the git repository::
@@ -57,19 +82,28 @@ The GUI widgets of the silx package depend on the following extra packages:
* `matplotlib <http://matplotlib.org/>`_ for the silx.gui.plot package
* `PyOpenGL <http://pyopengl.sourceforge.net/>`_ for the silx.gui.plot3d package
-Most modules and functions dealing with `HDF5 <https://www.hdfgroup.org/HDF5/>`_ input/output depend on the following extra package:
+Most modules and functions dealing with `HDF5 <https://www.hdfgroup.org/HDF5/>`_ input/output depend on:
+
* `h5py <http://www.h5py.org/>`_
-* `ipython <https://ipython.org/>`_ and `qtconsole <https://pypi.python.org/pypi/qtconsole>`_ is required by silx.gui.console.py
+Parallel algorithms depend on:
+
+* `PyOpenCL <https://documen.tician.de/pyopencl/>`_
+
+The console widgets depend on:
+
+* `ipython <https://ipython.org/>`_
+* `qtconsole <https://pypi.python.org/pypi/qtconsole>`_
+
Supported platforms: Linux, Windows, Mac OS X.
Documentation
-------------
-Documentation of releases is available at https://pythonhosted.org/silx/
+Documentation of latest release is available at http://www.silx.org/doc/silx/latest/
-Latest documentation (nightly build) is available at http://www.silx.org/doc/silx/
+Documentation of previous releases and nightly build is available at http://www.silx.org/doc/silx/
To build the documentation from the source (requires `Sphinx <http://www.sphinx-doc.org>`_), run::
@@ -93,18 +127,25 @@ To run the tests, from the source directory, run::
Examples
--------
-Some examples are available in the source code repository. For example::
-
- python examples/{exampleName.py}
+Some examples of sample code using silx are provided with the
+`silx documentation <http://www.silx.org/doc/silx/dev/sample_code/index.html>`_.
License
-------
-The source code of silx is licensed under the MIT and LGPL licenses.
-See the `copyright file <https://github.com/silx-kit/silx/blob/master/copyright>`_ for details.
+The source code of silx is licensed under the MIT license.
+See the `LICENSE <https://github.com/silx-kit/silx/blob/master/LICENSE>`_ and `copyright <https://github.com/silx-kit/silx/blob/master/copyright>`_ files for details.
+
+Citation
+--------
+
+silx releases can be cited by their DOI on Zenodo: |DOI:10.5281/zenodo.1000472|
.. |Travis Status| image:: https://travis-ci.org/silx-kit/silx.svg?branch=master
:target: https://travis-ci.org/silx-kit/silx
.. |Appveyor Status| image:: https://ci.appveyor.com/api/projects/status/qgox9ei0wxwfagrb/branch/master?svg=true
:target: https://ci.appveyor.com/project/ESRF/silx
+.. |DOI:10.5281/zenodo.1000472| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.1000472.svg
+ :target: https://doi.org/10.5281/zenodo.1000472
+
diff --git a/build-deb.sh b/build-deb.sh
new file mode 100755
index 0000000..064674c
--- /dev/null
+++ b/build-deb.sh
@@ -0,0 +1,239 @@
+#!/bin/sh
+#
+# Project: Silx
+# https://github.com/silx-kit/silx
+#
+# Copyright (C) 2015-2017 European Synchrotron Radiation Facility, Grenoble, France
+#
+# Principal author: Jérôme Kieffer (Jerome.Kieffer@ESRF.eu)
+#
+# 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
+
+# Script that builds a debian package from this library
+
+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)")
+
+deb_name=$(echo "$source_project" | tr '[:upper:]' '[:lower:]')
+
+# target system
+debian_version=$(grep -o '[0-9]*' /etc/issue)
+target_system=debian${debian_version}
+
+project_directory="`dirname \"$0\"`"
+project_directory="`( cd \"$project_directory\" && pwd )`" # absolutized
+dist_directory=${project_directory}/dist/${target_system}
+build_directory=${project_directory}/build/${target_system}
+
+if [ -d /usr/lib/ccache ];
+then
+ export PATH=/usr/lib/ccache:$PATH
+fi
+
+usage="usage: $(basename "$0") [options]
+
+Build the Debian ${debian_version} package of the ${project} library.
+
+If the build succeed the directory dist/debian${debian_version} will
+contains the packages.
+
+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
+"
+
+install=0
+use_python3=0 #used only for stdeb
+
+while :
+do
+ case "$1" in
+ -h | --help)
+ echo "$usage"
+ exit 0
+ ;;
+ --install)
+ install=1
+ shift
+ ;;
+ --python3)
+ use_python3=1
+ shift
+ ;;
+ --debian7)
+ debian_version=7
+ shift
+ ;;
+ --debian8)
+ debian_version=8
+ shift
+ ;;
+ --debian9)
+ debian_version=9
+ shift
+ ;;
+ -*)
+ echo "Error: Unknown option: $1" >&2
+ echo "$usage"
+ exit 1
+ ;;
+ *) # No more options
+ break
+ ;;
+ esac
+done
+
+clean_up()
+{
+ echo "Clean working dir:"
+ # clean up previous build
+ rm -rf ${build_directory}
+ # create the build context
+ mkdir -p ${build_directory}
+}
+
+build_deb_8_plus () {
+ echo "Build for debian 8 or newer using actual packaging"
+ tarname=${project}_${debianversion}.orig.tar.gz
+ clean_up
+ python setup.py debian_src
+ cp -f dist/${tarname} ${build_directory}
+ if [ -f dist/${project}-testimages.tar.gz ]
+ then
+ cp -f dist/${project}-testimages.tar.gz ${build_directory}
+ fi
+
+ cd ${build_directory}
+ tar -xzf ${tarname}
+
+ directory=${project}-${strictversion}
+ newname=${deb_name}_${debianversion}.orig.tar.gz
+
+ #echo tarname $tarname newname $newname
+ if [ $tarname != $newname ]
+ then
+ if [ -h $newname ]
+ then
+ rm ${newname}
+ fi
+ ln -s ${tarname} ${newname}
+ fi
+
+ if [ -f ${project}-testimages.tar.gz ]
+ then
+ if [ ! -h ${deb_name}_${debianversion}.orig-testimages.tar.gz ]
+ then
+ ln -s ${project}-testimages.tar.gz ${deb_name}_${debianversion}.orig-testimages.tar.gz
+ fi
+ fi
+
+ cd ${directory}
+ cp -r ${project_directory}/package/${target_system} debian
+ cp ${project_directory}/copyright debian
+
+ #handle test images
+ if [ -f ../${deb_name}_${debianversion}.orig-testimages.tar.gz ]
+ then
+ if [ ! -d testimages ]
+ then
+ mkdir testimages
+ fi
+ cd testimages
+ tar -xzf ../${deb_name}_${debianversion}.orig-testimages.tar.gz
+ cd ..
+ else
+ # Disable to skip tests during build
+ echo No test data
+ #export PYBUILD_DISABLE_python2=test
+ #export PYBUILD_DISABLE_python3=test
+ #export DEB_BUILD_OPTIONS=nocheck
+ fi
+
+ dch -v ${debianversion}-1 "upstream development build of ${project} ${version}"
+ dch --bpo "${project} snapshot ${version} built for ${target_system}"
+ dpkg-buildpackage -r
+ rc=$?
+
+ if [ $rc -eq 0 ]; then
+ # move packages to dist directory
+ echo Build succeeded...
+ rm -rf ${dist_directory}
+ mkdir -p ${dist_directory}
+ mv ${build_directory}/*.deb ${dist_directory}
+ mv ${build_directory}/*.x* ${dist_directory}
+ mv ${build_directory}/*.dsc ${dist_directory}
+ mv ${build_directory}/*.changes ${dist_directory}
+ cd ../../..
+ else
+ echo Build failed, please investigate ...
+ exit "$rc"
+ 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
+
+if [ $install -eq 1 ]; then
+ sudo -v su -c "dpkg -i ${dist_directory}/*.deb"
+fi
+
+exit "$rc"
diff --git a/copyright b/copyright
index a9b99ae..4229c58 100644
--- a/copyright
+++ b/copyright
@@ -7,15 +7,11 @@ Copyright: 2004-2017 European Synchrotron Radiation Facility
Data analysis unit (silx@esrf.fr)
License: MIT
-Files: silx/io/specfile/src/* silx/io/specfile/include/Lists.h silx/io/specfile/include/locale_management.h silx/io/specfile/include/SpecFile.h silx/io/specfile/include/SpecFileP.h
-Copyright: 2004-2016 European Synchrotron Radiation Facility
-License: LGPL-2.1+
-
Files: silx/math/histogramnd/include/msvc/stdint.h
Copyright: 2006-2008 Alexander Chemeris
License: BSD-3
-Files: silx/gui/plot/MPLColormap.py
+Files: silx/resources/gui/colormaps/inferno.npy silx/resources/gui/colormaps/magma.npy silx/resources/gui/colormaps/plasma.npy silx/resources/gui/colormaps/viridis.npy
Copyright: Nathaniel J. Smith, Stefan van der Walt, Eric Firing
License: CC0
@@ -68,45 +64,10 @@ License: BSD-3
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-License: GPL-3.0+
- This package is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License, or
- (at your option) any later version.
- .
- This package is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- .
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>
- .
- On Debian systems, the complete text of the GNU General
- Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
-
License: public-domain
You can use this free for any purpose. It's in the public domain. It
has no warranty
-License: LGPL-2.1+
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
- .
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
- .
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- .
- On Debian systems, the complete text of the GNU Lesser General Public
- License can be found in "/usr/share/common-licenses/LGPL-2.1".
-
License: CC0
Statement of Purpose
.
diff --git a/doc/source/Tutorials/fitconfig.rst b/doc/source/Tutorials/fitconfig.rst
index 1b5e8ae..225ef8f 100644
--- a/doc/source/Tutorials/fitconfig.rst
+++ b/doc/source/Tutorials/fitconfig.rst
@@ -122,7 +122,7 @@ used by our fit function to scale the *y* values.
self.scalingFactorEdit.setToolTip(
"Enter the scaling factor"
)
- self.scalingFactorEdit.setValidator(qt.QDoubleValidator())
+ self.scalingFactorEdit.setValidator(qt.QDoubleValidator(self))
self.ok = qt.QPushButton("ok", self)
self.ok.clicked.connect(self.accept)
diff --git a/doc/source/Tutorials/img/silx_view_edf.png b/doc/source/Tutorials/img/silx_view_edf.png
new file mode 100644
index 0000000..424c165
--- /dev/null
+++ b/doc/source/Tutorials/img/silx_view_edf.png
Binary files differ
diff --git a/doc/source/Tutorials/io.rst b/doc/source/Tutorials/io.rst
new file mode 100644
index 0000000..139ad2d
--- /dev/null
+++ b/doc/source/Tutorials/io.rst
@@ -0,0 +1,289 @@
+
+Getting started with silx.io
+============================
+
+This tutorial explains how to read data files using the :meth:`silx.io.open` function.
+
+The target audience are developers without knowledge of the *h5py* library.
+
+If you are already familiar with *h5py*, you just need to know that
+the :meth:`silx.io.open` function returns objects that mimic *h5py* file objects,
+and that the main supported file formats are:
+
+ - HDF5
+ - all formats supported by the *FabIO* library
+ - SPEC data files
+
+Knowledge about the python *dictionary* type and the numpy *ndarray* type
+are prerequisites for this tutorial.
+
+
+Background
+----------
+
+In the past, it was necessary to learn how to use multiple libraries to read multiple
+data formats. The library *FabIO* was designed to read images in many formats, but not to read
+more heterogeneous formats, such as *HDF5* or *SPEC*.
+
+To read *SPEC* data files in Python, a common solution was to use the *PyMca* module
+:mod:`PyMca5.PyMcaIO.specfilewrapper`.
+Regarding HDF5 files, the de-facto standard for reading them in Python is to
+use the *h5py* library.
+
+*silx* tries to address this situation by providing a unified way to read all
+data formats supported at the ESRF. Today, HDF5 is the preffered format to store
+data for many scientific institutions, including most synchrotrons.
+So it was decided to provide tools for reading data that mimic the *h5py* library's API.
+
+
+Definitions
+-----------
+
+HDF5
+++++
+
+The *HDF5* format is a *hierarchical data format*, designed to store and
+organize large amounts of data.
+
+A HDF5 file contains a number of *datasets*, which are multidimensional arrays
+of a homogeneous type.
+
+These datasets are stored in container structures
+called *groups*. Groups can also be stored in other groups, allowing to
+define a hierarchical tree structure.
+
+Both datasets and groups may have *attributes* attached to them. Attributes are
+used to document the object. They are similar to datasets in several ways
+(data container of homogeneous type), but they are typically much smaller.
+
+It is a common analogy to compare a HDF5 file to a filesystem.
+Groups are analogous to directories, while datasets are analogous to files,
+and attributes are analogous to file metadata (creation date, last modification...).
+
+.. image:: img/silx_view_edf.png
+ :width: 400px
+
+
+h5py
+++++
+
+The *h5py* library is a Pythonic interface to the `HDF5`_ binary data format.
+
+It exposes an HDF5 group as a python object that resembles a python
+dictionary, and an HDF5 dataset or attribute as an object that resembles a
+numpy array.
+
+API description
+---------------
+
+All main objects, File, Group and Dataset, share the following attributes:
+
+ - :attr:`attrs`: Attributes, as a dictionary of metadata for the group or dataset.
+ - :attr:`basename`: String giving the basename of this group or dataset.
+ - :attr:`name`: String giving the full path to this group or dataset, relative
+ to the root group (file).
+ - :attr:`file`: File object at the root of the tree structure containing this
+ group or dataset.
+ - :attr:`parent`: Group object containing this group or dataset.
+
+File object
++++++++++++
+
+The API of the file objects returned by the :meth:`silx.io.open`
+function tries to be as close as possible to the API of the :class:`h5py.File`
+objects used to read HDF5 data.
+
+A h5py file is a group with just a few extra attributes and methods.
+
+The objects defined in `silx.io` implement a subset of these attributes and methods:
+
+ - :attr:`filename`: Name of the file on disk.
+ - :attr:`mode`: String indicating if the file is open in read mode ("r")
+ or write mode ("w"). :meth:`silx.io.open` always returns objects in read mode.
+ - :meth:`close`: Close this file. All open objects will become invalid.
+
+The :attr:`parent` of a file is `None`, and its :attr:`name` is an empty string.
+
+Group object
+++++++++++++
+
+Group objects behave like python dictionaries.
+
+You can iterate over a group's :meth:`keys`, which are the names of the objects
+encapsulated by the group (datasets and sub-groups). The :meth:`values` method
+returns an iterator over the encapsulated objects. The :meth:`items` method returns
+an iterator over `(name, value)` pairs.
+
+Groups provide a :meth:`get` method that retrieves an item, or information about an item.
+Like standard python dictionaries, a `default` parameter can be used to specify
+a value to be returned if the given name is not a member of the group.
+
+Two methods are provided to visit recursively all members of a group, :meth:`visit`
+and :meth:`visititems`. The former takes as argument a *callable* with the signature
+``callable(name) -> None or return value``. The latter takes as argument a *callable*
+with the signature ``callable(name, object) -> None or return value`` (``object`` being a
+a group or dataset instance.)
+
+Example
+-------
+
+Accessing data
+++++++++++++++
+
+In this first example, we open a Spec data file and we print some of its information.
+
+.. code-block:: python
+
+ >>> import silx.io
+ >>> sf = silx.io.open("data/CuZnO_2.spec")
+ <silx.io.spech5.SpecH5 at 0x7f00d0760f90>
+ >>> print(sf.keys())
+ ['1.1', '2.1', '3.1', '4.1', '5.1', '6.1', '7.1', ...]
+ >>> print(sf["1.1"])
+ <silx.io.spech5.ScanGroup object at 0x7f00d0715b90>
+
+
+We just opened a file, keeping a reference to the file object as ``sf``.
+We then printed all items contained in this root group. We can see that all
+these items are groups. Lets looks at what is inside these groups, and find
+datasets:
+
+
+.. code-block:: python
+
+ >>> grp = sf["2.1"]
+ ... for name in grp:
+ ... item = grp[name]
+ ... print("Found item " + name)
+ ... if silx.io.is_dataset(item):
+ ... print("'%s' is a dataset.\n" % name)
+ ... elif silx.io.is_group(item):
+ ... print("'%s' is a group.\n" % name)
+ ...
+ Found item title
+ title is a dataset.
+
+ Found item start_time
+ start_time is a dataset.
+
+ Found item instrument
+ instrument is a group.
+
+ Found item measurement
+ measurement is a group.
+
+ Found item sample
+ sample is a group.
+
+We could have replaced the first three lines with this single line,
+by iterating over the iterator returned by the group method :meth:`items`:
+
+.. code-block:: python
+
+ >>> for name, item in sf["2.1"].items():
+ ...
+
+In addition to :meth:`silx.io.is_group` and :meth:`silx.io.is_dataset`,
+you can also use :meth:`silx.io.is_file` and :meth:`silx.io.is_softlink`.
+
+
+Let's look at a dataset:
+
+.. code-block:: python
+
+ >>> print(sf["2.1/title"])
+ <HDF5-like dataset "title": shape (), type "|S29">
+
+As you can see, printing a dataset does not print the data itself, it only print a
+representation of the dataset object. The information printed tells us that the
+object is similar to a numpy array, with a *shape* and a *type*.
+
+In this case, we are dealing with a scalar dataset, so we can use the same syntax as
+in numpy to access the scalar value, ``result = dset[()]``:
+
+.. code-block:: python
+
+ >>> print(sf["2.1/title"][()])
+ 2 ascan phi 0.61 1.61 20 1
+
+Similarly, you need to use numpy slicing to access values in numeric array:
+
+.. code-block:: python
+
+ >>> print (sf["2.1/measurement/Phi"])
+ <HDF5-like dataset "Phi": shape (21,), type "<f4">
+ >>> print (sf["2.1/measurement/Phi"][0:10])
+ [ 0.61000001 0.66000003 0.70999998 0.75999999 0.81 0.86000001
+ 0.91000003 0.95999998 1.00999999 1.05999994]
+ >>> entire_phi_array = sf["2.1/measurement/Phi"][:]
+
+Here we could read the entire array by slicing it with ``[:]``, because we know
+it is a 1D array. For a 2D array, the slicing argument would have been ``[:, :]``.
+
+For a dataset of unknown dimensionality (including scalar datasets), the
+``Ellipsis`` object (represented by ``...``) can be used to slice the object.
+
+.. code-block:: python
+
+ >>> print(sf["2.1/title"][...])
+ 2 ascan phi 0.61 1.61 20 1
+ >>> print (sf["2.1/measurement/Phi"][...])
+ [ 0.61000001 0.66000003 0.70999998 0.75999999 0.81 0.86000001
+ 0.91000003 0.95999998 1.00999999 1.05999994 1.11000001 1.15999997
+ 1.21000004 1.25999999 1.30999994 1.36000001 1.40999997 1.46000004
+ 1.50999999 1.55999994 1.61000001]
+
+To read more about the usage of ``Ellipsis`` to slice arrays, see
+`Indexing numpy arrays <http://scipy-cookbook.readthedocs.io/items/Indexing.html?highlight=indexing#Multidimensional-slices>`_
+in the scipy documentation.
+
+Note that slicing a scalar dataset with ``[()]`` is not strictly equivalent to
+slicing with ``[...]``. The former gives you the actual scalar value in
+the dataset, while the latter always gives you an array object, which happens to
+be 0D in the case of a scalar.
+
+ >>> sf["2.1/instrument/positioners/Delta"][()]
+ 0.0
+ >>> sf["2.1/instrument/positioners/Delta"][...]
+ array(0.0, dtype=float32)
+
+Closing the file
+++++++++++++++++
+
+You should always make sure to close the files that you opened. The simple way of
+closing a file is to call its :meth:`close` method.
+
+.. code-block:: python
+
+ import silx.io
+ sf = silx.io.open("data/CuZnO_2.spec")
+
+ # read the information you need...
+ maxPhi = sf["2.1/measurement/Phi"][...].max()
+
+ sf.close()
+
+The drawback of this method is that, if an error is raised while processing
+the file, the program might never reach the ``sf.close()`` line.
+Leaving files open can cause various issues for the rest of your program,
+such as consuming memory, not being able to reopen the file when you need it...
+
+The best way to ensure the file is always properly closed is to use the file
+inside its context manager:
+
+.. code-block:: python
+
+ import silx.io
+
+ with silx.io.open("data/CuZnO_2.spec") as sf:
+ # read the information you need...
+ maxPhi = sf["2.1/measurement/Phi"][...].max()
+
+
+Additional resources
+--------------------
+
+- `h5py documentation <http://docs.h5py.org/en/latest/>`_
+- `Formats supported by FabIO <http://www.silx.org/doc/fabio/dev/getting_started.html#list-of-file-formats-that-fabio-can-read-and-write>`_
+- `Spec file h5py-like structure <http://www.silx.org/doc/silx/dev/modules/io/spech5.html#api-description>`_
+- `HDF5 format documentation <https://support.hdfgroup.org/HDF5/>`_
diff --git a/doc/source/Tutorials/specfile_to_hdf5.rst b/doc/source/Tutorials/specfile_to_hdf5.rst
index 31f8383..32d942a 100644
--- a/doc/source/Tutorials/specfile_to_hdf5.rst
+++ b/doc/source/Tutorials/specfile_to_hdf5.rst
@@ -126,8 +126,7 @@ Example of scan and data block, without MCA::
29.366004 45256 0.000343 734 419 248 229 236 343 178082 664 0.00372862 0.00765939 0.0041217 0.00235285 0.00185308 0.00139262 0.00128592 0.00132523 0.00192608 1364 330
29.36998 45258 0.00036 847 448 254 229 248 360 178342 668 0.00374561 0.00857342 0.0047493 0.00251203 0.00194009 0.00142423 0.00128405 0.00139059 0.00201859 1529 346
-Synthetic example of file with 3 scans. The last scan includes data of 3 multichannel analysers, sharing the
-same MCA header.
+Synthetic example of file with 3 scans. The last scan includes MCA data.
::
@@ -198,8 +197,8 @@ The structure exposed is as follows::
start_time = "…"
instrument/
specfile/
- file_header = ["…", "…", …]
- scan_header = ["…", "…", …]
+ file_header = "…"
+ scan_header = "…"
positioners/
motor_name = value
@@ -222,6 +221,11 @@ The structure exposed is as follows::
data -> /1.1/instrument/mca_0/data
info -> /1.1/instrument/mca_0/
+ sample/
+ ub_matrix = …
+ unit_cell = …
+ unit_cell_abc = …
+ unit_cell_alphabetagamma = …
2.1/
@@ -292,12 +296,15 @@ Files and groups can be treated as iterators, which allows looping through them.
Converting SPEC data to HDF5
++++++++++++++++++++++++++++
-The *silx* module :mod:`silx.io.spectoh5` can be used to convert a SPEC file into a
+Using the convert module
+************************
+
+The *silx* module :mod:`silx.io.convert` can be used to convert a SPEC file into a
HDF5 file with the same structure as the one exposed by the :mod:`spech5` module.
.. code-block:: python
- from silx.io.spectoh5 import convert
+ from silx.io.convert import convert
convert("/home/pierre/myspecfile.dat", "myfile.h5")
@@ -305,10 +312,10 @@ HDF5 file with the same structure as the one exposed by the :mod:`spech5` module
You can then read the file with any HDF5 reader.
-In addition to the function :func:`silx.io.spectoh5.convert`, which is simplified
-on purpose, you can use the more flexible :func:`silx.io.spectoh5.write_spec_to_h5`.
+The function :func:`silx.io.convert.convert` is a simplified version of a
+more flexible function :func:`silx.io.convert.write_to_h5`.
-This way, you can choose to write scans into a specific HDF5 group in the output directory.
+The latter allows you to write scans into a specific HDF5 group in the output directory.
You can also decide whether you want to overwrite an existing file, or append data to it.
You can specify whether existing data with the same name as input data should be overwritten
or ignored.
@@ -316,8 +323,34 @@ or ignored.
This allows you to repeatedly transfer new content of a SPEC file to an existing HDF5 file, in between
two scans.
-The following script is an example of a command line interface to :func:`write_spec_to_h5`.
+The following script is an example of a command line interface to :func:`write_to_h5`.
+
+.. literalinclude:: ../../../examples/writetoh5.py
+ :lines: 44-
+
+Using the convert application
+*****************************
+
+.. versionadded:: 0.6
+
+*silx* also provides a ``silx convert`` command line application, which allows you to
+perform standard conversions without having to write your own program.
+
+Type ``silx convert --help`` in a terminal to see all available options.
+
+The simplest command to convert a single SPEC file to an HDF5 file would be::
+
+ silx convert myspecfile.dat
+
+As no output name is supplied, the input file name is reused but the extension is
+modified from *.dat* to *.h5*.
+
+The following example allows you to append the content of a SPEC file to an
+existing HDF5 file::
+
+ silx convert myspecfile.dat -m a -o myhdf5file.h5
-.. literalinclude:: ../../../examples/spectoh5.py
- :lines: 42-
+You could write the file into a specific group of the HDF5 file by providing
+the complete URI in the format ``file_path::group_path``. For instance::
+ silx convert myspecfile.dat -m a -o archive.h5::/2017-09-20/SPEC
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 30abf54..e724d3c 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -1,18 +1,40 @@
# -*- coding: utf-8 -*-
+# /*##########################################################################
+# Copyright (C) 2015-2017 European Synchrotron Radiation Facility
#
-# silx documentation build configuration file, created by
-# sphinx-quickstart on Fri Nov 27 14:20:46 2015.
+# 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:
#
-# This file is execfile()d with the current directory set to its containing dir.
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
+# 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.
#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
+# ############################################################################*/
+"""silx documentation build configuration file, created by
+sphinx-quickstart on Fri Nov 27 14:20:46 2015.
+
+This file is execfile()d with the current directory set to its containing dir.
+
+Note that not all possible configuration values are present in this
+autogenerated file.
+
+All configuration values have a default; values that are commented out
+serve to show the default."""
import sys
import os
+import os.path
import glob
import subprocess
@@ -20,11 +42,18 @@ import subprocess
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
# sys.path.insert(0, os.path.abspath('.'))
-dirname = os.path.dirname
-root_dir = dirname(dirname(dirname(os.path.abspath(__file__))))
-import silx
-source_dir = dirname(dirname(silx.__file__))
-os.environ["PYTHONPATH"] = source_dir + os.pathsep + os.environ.get("PYTHONPATH", "")
+project = u'silx'
+try:
+ import silx
+ project_dir = os.path.abspath(os.path.join(__file__, "..", "..", ".."))
+ build_dir = os.path.abspath(silx.__file__)
+ if not build_dir.startswith(project_dir):
+ raise RuntimeError("%s looks to come from the system. Fix your PYTHONPATH and restart sphinx." % project)
+except ImportError:
+ raise RuntimeError("%s is not on the path. Fix your PYTHONPATH and restart sphinx." % project)
+
+# Add local sphinx extension directory
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ext'))
# -- General configuration -----------------------------------------------------
@@ -38,7 +67,8 @@ extensions = [
'sphinx.ext.coverage',
'sphinx.ext.mathjax',
'sphinx.ext.viewcode',
- 'sphinx.ext.doctest'
+ 'sphinx.ext.doctest',
+ 'sphinxext-archive'
]
autodoc_member_order = 'bysource'
@@ -56,7 +86,6 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
-project = u'silx'
from silx._version import strictversion, version, __date__ as _date
year = _date.split("/")[-1]
copyright = u'2015-%s, Data analysis unit, European Synchrotron Radiation Facility, Grenoble' % year
@@ -138,7 +167,7 @@ html_favicon = "img/silx.ico"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-# html_static_path = ['_static']
+# html_static_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
diff --git a/doc/source/description/img/sift_frame_ROI.png b/doc/source/description/img/sift_frame_ROI.png
new file mode 100644
index 0000000..f722b35
--- /dev/null
+++ b/doc/source/description/img/sift_frame_ROI.png
Binary files differ
diff --git a/doc/source/description/img/sift_match2.png b/doc/source/description/img/sift_match2.png
new file mode 100644
index 0000000..a3beb3c
--- /dev/null
+++ b/doc/source/description/img/sift_match2.png
Binary files differ
diff --git a/doc/source/description/sift.rst b/doc/source/description/sift.rst
index bd89dd0..d4a9fad 100644
--- a/doc/source/description/sift.rst
+++ b/doc/source/description/sift.rst
@@ -246,7 +246,7 @@ Matching is also explained in this tutorial, once the keypoints are
:alt: Example of image matching for pattern recognition
-.. figure:: img/sift_match2.jpg
+.. figure:: img/sift_match2.png
:align: center
:alt: Another example of image matching for pattern recognition
@@ -382,7 +382,7 @@ For instance, if a sample is centered on the image, the user can select the
center of the image before processing.
-.. figure:: img/sift_frame_ROI.jpg
+.. figure:: img/sift_frame_ROI.png
:align: center
:alt: Sample with region of interest
diff --git a/doc/source/ext/sphinxext-archive.py b/doc/source/ext/sphinxext-archive.py
new file mode 100644
index 0000000..dc1c2c8
--- /dev/null
+++ b/doc/source/ext/sphinxext-archive.py
@@ -0,0 +1,184 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2017 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.
+#
+# ###########################################################################*/
+"""Sphinx extension creating a link to an archive.
+
+This Sphinx extension adds the possibility to create a zip archive and
+include it in the generated HTML documentation.
+
+This extension provides an *archive* directive.
+
+Usage:
+
+.. archive:: <relative_path/to/directory/to/compress>
+ :filename: <name_of_the_archive_file, default: last_folder_of_directory.zip>
+ :filter: <Space-separated list of included file patterns, default *.*>
+ :basedir: <Name of the base directory in the archive, default: filename>
+
+Examples:
+
+To create a example.zip archive of the ../../example/ folder:
+
+.. archive:: ../../examples/
+
+To get more control on the name of the archive and its content:
+
+.. archive:: ../../examples/
+ :filename: myproject_examples.zip
+ :filter: *.py *.png
+ :basedir: myproject_examples
+
+WARNING: The content of this directory is not checked for outdated documents.
+"""
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "01/09/2017"
+
+
+import fnmatch
+import logging
+import os
+import os.path
+import posixpath
+import shutil
+import tempfile
+
+from docutils.parsers.rst import directives, Directive
+import docutils.nodes
+
+
+_logger = logging.getLogger(__name__)
+
+
+# docutils directive
+
+class ArchiveDirective(Directive):
+ """Add a link to download an archive
+
+ This directive add an :class:`archive` node to the document tree.
+ """
+
+ has_content = False
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = False
+ option_spec = {
+ 'filename': directives.unchanged,
+ 'filter': directives.unchanged,
+ 'basedir': directives.unchanged,
+ }
+
+ def run(self):
+ node = archive('')
+
+ # Get rst source file containing this directive
+ source_file = self.state_machine.get_source_and_line()[0]
+ if source_file is None:
+ raise RuntimeError('Cannot get rst source file path')
+
+ # Build input path from rst source file and directive argument
+ input_path = self.arguments[0]
+ if not input_path.startswith('/'): # Argument is a relative path
+ input_path = os.path.abspath(
+ os.path.join(os.path.dirname(source_file), input_path))
+ node['input_path'] = input_path
+
+ default_basedir = os.path.basename(input_path)
+ node['basedir'] = self.options.get('basedir', default_basedir)
+ node['filename'] = self.options.get('filename',
+ '.'.join((default_basedir, 'zip')))
+
+ node['filter'] = self.options.get('filter', '*.*')
+
+ return [node]
+
+
+# archive doctuils node
+
+class archive(docutils.nodes.General, docutils.nodes.Element, docutils.nodes.Inline):
+ """archive node created by :class:`ArchiveDirective`"""
+ pass
+
+
+def visit_archive_html(self, node):
+ """Node visitor to translate :class:`archive` nodes to HTML.
+
+ :param self: Sphinx HTML Writter
+ :param node: The :class:`archive` node to translate to HTML
+ :raise: SkipNode as depart is not implemented
+ """
+ filename = node['filename']
+ input_path = node['input_path']
+
+ # Create a temporary folder to create archive content
+ tmp_dir = tempfile.mkdtemp()
+
+ # Copy selected content to temporary folder
+ base_dir = os.path.join(tmp_dir, node['basedir'])
+
+ def ignore(src, names):
+ patterns = node['filter'].split()
+ ignored_names = []
+ for name in names:
+ for pattern in patterns:
+ if fnmatch.fnmatch(name, pattern):
+ break
+ else:
+ ignored_names.append(name)
+ return ignored_names
+
+ shutil.copytree(input_path, base_dir, ignore=ignore)
+
+ # Compress temporary folder to zip
+ output_filename = os.path.join(
+ self.builder.outdir, '_downloads', filename)
+ root, ext = os.path.splitext(output_filename)
+ assert ext == '.zip'
+ shutil.make_archive(root, 'zip', tmp_dir, node['basedir'])
+
+ # Clean-up temporary folder
+ shutil.rmtree(tmp_dir)
+
+ # Generate HTML
+ relative_path = posixpath.join(self.builder.dlpath, filename)
+ self.body.append('<a href="%s">%s</a>' % (relative_path, filename))
+ raise docutils.nodes.SkipNode
+
+
+def visit_skip(self, node):
+ """No-op node visitor"""
+ raise docutils.nodes.SkipNode
+
+
+# Extension setup
+
+def setup(app):
+ """Sphinx extension registration"""
+ app.add_node(archive,
+ html=(visit_archive_html, None),
+ latex=(visit_skip, None))
+
+ app.add_directive('archive', ArchiveDirective)
+
+ return {'version': '0.1'}
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 656f774..86a1395 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -1,10 +1,16 @@
silx |version|
==============
-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 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.
+Silx can be cited by its DOIs referenced on `Zenodo <https://doi.org/10.5281/zenodo.1000472>`_.
-The current version provides reading `SPEC <https://certif.com/spec.html>`_ file format, histogramming, fitting, curves and image plot widget with a set of associated tools.
+The current version provides reading `SPEC <https://certif.com/spec.html>`_ file
+format, histogramming, fitting, curves and image plot widget with a set of
+associated tools.
.. toctree::
:hidden:
@@ -33,6 +39,9 @@ The current version provides reading `SPEC <https://certif.com/spec.html>`_ file
:doc:`modules/index`
Documentation of the packages included in *silx*
+:doc:`modules/gui/gallery`
+ Widgets gallery and screenshots
+
:doc:`changelog`
List of changes between releases
diff --git a/doc/source/license.rst b/doc/source/license.rst
index 6b38b8e..760be91 100644
--- a/doc/source/license.rst
+++ b/doc/source/license.rst
@@ -1,7 +1,9 @@
License
=======
-The source code of *silx* is mostly licensed under the `MIT <https://opensource.org/licenses/MIT>`_ and `LGPL 2.1 <https://opensource.org/licenses/LGPL-2.1>`_ licenses.
+The source code of *silx* is licensed under the `MIT <https://opensource.org/licenses/MIT>`_ license:
+
+.. include:: ../../LICENSE
The following list provides the copyright and license of the different source files of the project:
diff --git a/doc/source/modules/gui/data/img/ArrayTableWidget.png b/doc/source/modules/gui/data/img/ArrayTableWidget.png
new file mode 100644
index 0000000..7c81d02
--- /dev/null
+++ b/doc/source/modules/gui/data/img/ArrayTableWidget.png
Binary files differ
diff --git a/doc/source/modules/gui/fit/backgroundwidget.rst b/doc/source/modules/gui/fit/backgroundwidget.rst
index eea2052..a34c639 100644
--- a/doc/source/modules/gui/fit/backgroundwidget.rst
+++ b/doc/source/modules/gui/fit/backgroundwidget.rst
@@ -7,13 +7,6 @@
.. automodule:: silx.gui.fit.BackgroundWidget
-.. |imgBGWidget| image:: ./img/bgwidget.png
- :height: 300px
- :align: middle
-
-|imgBGWidget|
-
-
API
---
diff --git a/doc/source/modules/gui/fit/img/BackgroundDialog.png b/doc/source/modules/gui/fit/img/BackgroundDialog.png
new file mode 100644
index 0000000..c6b8c03
--- /dev/null
+++ b/doc/source/modules/gui/fit/img/BackgroundDialog.png
Binary files differ
diff --git a/doc/source/modules/gui/fit/img/FitWidget.png b/doc/source/modules/gui/fit/img/FitWidget.png
new file mode 100644
index 0000000..73a80b4
--- /dev/null
+++ b/doc/source/modules/gui/fit/img/FitWidget.png
Binary files differ
diff --git a/doc/source/modules/gui/fit/img/bgwidget.png b/doc/source/modules/gui/fit/img/bgwidget.png
deleted file mode 100644
index 724eadb..0000000
--- a/doc/source/modules/gui/fit/img/bgwidget.png
+++ /dev/null
Binary files differ
diff --git a/doc/source/modules/gui/fit/index.rst b/doc/source/modules/gui/fit/index.rst
index 67c22a1..d9581ed 100644
--- a/doc/source/modules/gui/fit/index.rst
+++ b/doc/source/modules/gui/fit/index.rst
@@ -9,11 +9,11 @@
Snapshot of the widgets:
-.. |imgFitWidget| image:: ../../../Tutorials/img/fitwidget4.png
+.. |imgFitWidget| image:: ./img/FitWidget.png
:height: 150px
:align: middle
-.. |imgBGWidget| image:: ./img/bgwidget.png
+.. |imgBGWidget| image:: ./img/BackgroundDialog.png
:height: 150px
:align: middle
diff --git a/doc/source/modules/gui/gallery.rst b/doc/source/modules/gui/gallery.rst
new file mode 100644
index 0000000..4ae83d6
--- /dev/null
+++ b/doc/source/modules/gui/gallery.rst
@@ -0,0 +1,273 @@
+
+Widgets gallery
+===============
+
+
+:mod:`silx.gui.console` Widgets
++++++++++++++++++++++++++++++++
+
+.. currentmodule:: silx.gui.console
+
+.. list-table::
+ :widths: 1 4
+ :header-rows: 1
+
+ * - Widget
+ - Description
+ * - .. image:: img/IPythonWidget.png
+ :height: 150px
+ :align: center
+ - :class:`IPythonWidget` is an interactive console widget running a
+ :class`QtInProcessKernelManager`. This allows to push variables to the
+ interactive console, and interact with your application (e.g. adding
+ curves to a plot)
+ * - .. image:: img/IPythonDockWidget.png
+ :height: 150px
+ :align: center
+ - :class:`IPythonDockWidget` is an :class:`IPythonWidget` embedded in
+ a :class:`QDockWidget`.
+
+
+:mod:`silx.gui.data` Widgets
+++++++++++++++++++++++++++++
+
+.. currentmodule:: silx.gui.data
+
+.. list-table::
+ :widths: 1 4
+ :header-rows: 1
+
+ * - Widget
+ - Description
+ * - .. image:: data/img/ArrayTableWidget.png
+ :height: 150px
+ :align: center
+ - :class:`ArrayTableWidget` is a table widget with browsers designed to
+ display the content of multi-dimensional data arrays.
+ * - .. image:: data/img/DataViewer.png
+ :height: 150px
+ :align: center
+ - :class:`DataViewer` is a widget designed to display data using the most
+ adapted view.
+ * - .. image:: data/img/DataViewerFrame.png
+ :height: 150px
+ :align: center
+ - :class:`DataViewerFrame` is a :class:`DataViewer` with a view selector
+ that lets you view the data using any compatible view.
+ * - .. image:: data/img/NumpyAxesSelector.png
+ :height: 50px
+ :align: center
+ - :class:`NumpyAxesSelector` is a widget designed to select a subarray in a
+ n-dimensional array, by fixing the index on some of the dimensions.
+
+
+:mod:`silx.gui.fit` Widgets
++++++++++++++++++++++++++++
+
+.. currentmodule:: silx.gui.fit
+
+.. list-table::
+ :widths: 1 4
+ :header-rows: 1
+
+ * - Widget
+ - Description
+ * - .. image:: fit/img/FitWidget.png
+ :height: 150px
+ :align: center
+ - :class:`FitWidget` is a widget designed to configure and run a fitting process,
+ with constraints on parameters.
+ * - .. image:: fit/img/BackgroundDialog.png
+ :height: 150px
+ :align: center
+ - :class:`BackgroundWidget.BackgroundDialog` is a widget designed to adjust
+ the parameters and preview the results of a *snip* or *strip* background
+ filter.
+
+
+:mod:`silx.gui.hdf5` Widgets
+++++++++++++++++++++++++++++
+
+.. currentmodule:: silx.gui.hdf5
+
+.. list-table::
+ :widths: 1 4
+ :header-rows: 1
+
+ * - Widget
+ - Description
+ * - .. image:: hdf5/img/Hdf5TreeView.png
+ :height: 150px
+ :align: center
+ - :class:`Hdf5TreeView` is a tree view desiged to browse an HDF5
+ file structure.
+
+.. _plot-gallery:
+
+:mod:`silx.gui.plot` Widgets
+++++++++++++++++++++++++++++
+
+.. currentmodule:: silx.gui.plot
+
+Plotting widgets:
+
+.. list-table::
+ :widths: 1 4
+ :header-rows: 1
+
+ * - Widget
+ - Description
+ * - .. image:: plot/img/PlotWidget.png
+ :height: 150px
+ :align: center
+ - :class:`PlotWidget` is the base Qt widget providing a plot area.
+ Other plot widgets are based on this one and provides the same API.
+ * - .. image:: plot/img/PlotWindow.png
+ :height: 150px
+ :align: center
+ - :class:`PlotWindow` adds a toolbar to :class:`PlotWidget`.
+ The content of this toolbar can be configured from the
+ :class:`PlotWindow` constructor or by hiding its content afterward.
+ * - .. image:: plot/img/Plot1D.png
+ :height: 150px
+ :align: center
+ - :class:`.Plot1D` is a :class:`PlotWindow` configured with tools useful
+ for curves.
+ * - .. image:: plot/img/Plot2D.png
+ :height: 150px
+ :align: center
+ - :class:`.Plot2D` is a :class:`PlotWindow` configured with tools useful
+ for images.
+ * - .. image:: plot/img/ImageView.png
+ :height: 150px
+ :align: center
+ - :class:`ImageView` adds side histograms to a :class:`.Plot2D` widget.
+ * - .. image:: plot/img/StackView.png
+ :height: 150px
+ :align: center
+ - :class:`StackView` is a widget designed to display an image from a
+ stack of images in a :class:`PlotWindow` widget, with a frame browser
+ to navigate in the stack. The profile tool can do a 2D profile on the
+ stack of images.
+ * - .. image:: plot/img/ComplexImageView.png
+ :height: 150px
+ :align: center
+ - :class:`ComplexImageView` is a widget dedicated to visualize a single
+ 2D dataset of complex data.
+ It allows to switch between viewing amplitude, phase, real, imaginary,
+ colored phase with amplitude or log10(amplitude) as brightness.
+
+
+Additional widgets:
+
+.. list-table::
+ :widths: 1 4
+ :header-rows: 1
+
+ * - Widget
+ - Description
+ * - .. image:: plot/img/PositionInfo.png
+ :width: 300px
+ :align: center
+ - :class:`.PlotTools.PositionInfo` is a widget displaying mouse position and
+ information of a :class:`PlotWidget` associated to the mouse position.
+ * - .. image:: plot/img/LimitsToolBar.png
+ :width: 300px
+ :align: center
+ - :class:`.PlotTools.LimitsToolBar` is a QToolBar displaying and
+ controlling the limits of a :class:`PlotWidget`.
+ * - .. image:: plot/img/logColorbar.png
+ :height: 150px
+ :align: center
+ - :class:`.ColorBar.ColorBarWidget` display colormap gradient and can be linked with a plot
+ to display the colormap
+
+.. _plot3d-gallery:
+
+:mod:`silx.gui.plot3d` Widgets
+++++++++++++++++++++++++++++++
+
+.. currentmodule:: silx.gui.plot3d
+
+.. list-table::
+ :widths: 1 4
+ :header-rows: 1
+
+ * - Widget
+ - Description
+ * - .. image:: plot3d/img/ScalarFieldView.png
+ :height: 150px
+ :align: center
+ - :class:`ScalarFieldView` is a :class:`Plot3DWindow` dedicated to display 3D scalar field.
+ It can display iso-surfaces and an interactive cutting plane.
+ Sample code: :doc:`plot3d/viewer3dvolume_example`.
+ * - .. image:: plot3d/img/Plot3DWindow.png
+ :height: 150px
+ :align: center
+ - :class:`Plot3DWindow` is a :class:`QMainWindow` with a :class:`Plot3DWidget` as central widget
+ and toolbars.
+ * - .. image:: plot3d/img/Plot3DWidget.png
+ :height: 150px
+ :align: center
+ - :class:`Plot3DWidget` is the base Qt widget providing an OpenGL 3D scene.
+ Other widgets are using this widget as the OpenGL scene canvas.
+ * - .. image:: plot3d/img/SFViewParamTree.png
+ :height: 150px
+ :align: center
+ - :class:`SFViewParamTree` is a :class:`QTreeView` widget that can be attached to a :class:`ScalarFieldView`.
+ It displays current parameters of the :class:`ScalarFieldView` and allows to modify it.
+ Sample code: :doc:`plot3d/viewer3dvolume_example`.
+
+
+:mod:`silx.gui.widgets` Widgets
++++++++++++++++++++++++++++++++
+
+.. currentmodule:: silx.gui.widgets
+
+.. list-table::
+ :widths: 1 4
+ :header-rows: 1
+
+ * - Widget
+ - Description
+ * - .. image:: widgets/img/FrameBrowser.png
+ :width: 110px
+ :align: center
+ - :class:`FrameBrowser.FrameBrowser` is a browser widget designed to
+ browse through a sequence of integers (e.g. the indices of an array)
+ * - .. image:: widgets/img/HorizontalSliderWithBrowser.png
+ :width: 150px
+ :align: center
+ - :class:`FrameBrowser.HorizontalSliderWithBrowser` is a :class:`FrameBrowser`
+ with an additional slider.
+ * - .. image:: widgets/img/PeriodicCombo.png
+ :width: 150px
+ :align: center
+ - :class:`PeriodicTable.PeriodicCombo` is a :class:`QComboBox` widget designed to
+ select a single atomic element.
+ * - .. image:: widgets/img/PeriodicList.png
+ :height: 150px
+ :align: center
+ - :class:`PeriodicTable.PeriodicList` is a :class:`QTreeWidget` designed to select one
+ or more atomic elements.
+ * - .. image:: widgets/img/PeriodicTable.png
+ :height: 150px
+ :align: center
+ - :class:`PeriodicTable.PeriodicTable` is a periodic table widget designed to select one
+ or more atomic elements.
+ * - .. image:: widgets/img/TableWidget.png
+ :height: 150px
+ :align: center
+ - :class:`TableWidget.TableWidget` and :class:`TableWidget.TableView` inherit respectively
+ :class:`QTableWidget` and :class:`QTableView`, and add a context menu with *cut/copy/paste*
+ actions.
+ * - .. image:: widgets/img/WaitingPushButton.png
+ :width: 60px
+ :align: center
+ - :class:`WaitingPushButton` is a :class:`QPushButton` that can be graphically disabled,
+ for example to wait for a callback function to finish computing.
+ * - .. image:: widgets/img/ThreadPoolPushButton.png
+ :width: 100px
+ :align: center
+ - :class:`ThreadPoolPushButton` is a :class:`WaitingPushButton` that executes a
+ callback in a thread.
diff --git a/doc/source/modules/gui/icons.rst b/doc/source/modules/gui/icons.rst
index 61ea71d..c3fefd8 100644
--- a/doc/source/modules/gui/icons.rst
+++ b/doc/source/modules/gui/icons.rst
@@ -25,16 +25,22 @@ Available icons
- 3d-plane-normal-y
* - |3d-plane-normal-z|
- 3d-plane-normal-z
+ * - |3d-plane-pan|
+ - 3d-plane-pan
* - |3d-plane|
- 3d-plane
* - |arrow-keys|
- arrow-keys
+ * - |axis|
+ - axis
* - |camera|
- camera
* - |clipboard|
- clipboard
* - |close|
- close
+ * - |colorbar|
+ - colorbar
* - |colormap|
- colormap
* - |crop|
@@ -97,10 +103,14 @@ Available icons
- item-3dim
* - |item-ndim|
- item-ndim
+ * - |item-none|
+ - item-none
* - |item-object|
- item-object
* - |last|
- last
+ * - |math-amplitude|
+ - math-amplitude
* - |math-average|
- math-average
* - |math-derive|
@@ -109,6 +119,8 @@ Available icons
- math-energy
* - |math-fit|
- math-fit
+ * - |math-imaginary|
+ - math-imaginary
* - |math-normalize|
- math-normalize
* - |math-peak-reset|
@@ -117,6 +129,14 @@ Available icons
- math-peak-search
* - |math-peak|
- math-peak
+ * - |math-phase-color-log|
+ - math-phase-color-log
+ * - |math-phase-color|
+ - math-phase-color
+ * - |math-phase|
+ - math-phase
+ * - |math-real|
+ - math-real
* - |math-sigma|
- math-sigma
* - |math-smooth|
@@ -127,10 +147,14 @@ Available icons
- math-swap-sign
* - |math-ymin-to-zero|
- math-ymin-to-zero
+ * - |median-filter|
+ - median-filter
* - |next|
- next
* - |normal|
- normal
+ * - |pan|
+ - pan
* - |pixel-intensities|
- pixel-intensities
* - |plot-grid|
@@ -167,12 +191,16 @@ Available icons
- plot-yup
* - |previous|
- previous
+ * - |profile-clear|
+ - profile-clear
* - |profile1D|
- profile1D
* - |profile2D|
- profile2D
* - |remove|
- remove
+ * - |rotate-3d|
+ - rotate-3d
* - |rudder|
- rudder
* - |selected|
@@ -205,8 +233,6 @@ Available icons
- sliders-on
* - |spec|
- spec
- * - |test-png|
- - test-png
* - |view-1d|
- view-1d
* - |view-2d-stack|
@@ -217,6 +243,10 @@ Available icons
- view-3d
* - |view-fullscreen|
- view-fullscreen
+ * - |view-hdf5|
+ - view-hdf5
+ * - |view-nexus|
+ - view-nexus
* - |view-nofullscreen|
- view-nofullscreen
* - |view-raw|
@@ -227,6 +257,8 @@ Available icons
- view-text
* - |window-new|
- window-new
+ * - |zoom-back|
+ - zoom-back
* - |zoom-in|
- zoom-in
* - |zoom-original|
@@ -239,11 +271,14 @@ Available icons
.. |3d-plane-normal-x| image:: ../../../../silx/resources/gui/icons/3d-plane-normal-x.png
.. |3d-plane-normal-y| image:: ../../../../silx/resources/gui/icons/3d-plane-normal-y.png
.. |3d-plane-normal-z| image:: ../../../../silx/resources/gui/icons/3d-plane-normal-z.png
+.. |3d-plane-pan| image:: ../../../../silx/resources/gui/icons/3d-plane-pan.png
.. |3d-plane| image:: ../../../../silx/resources/gui/icons/3d-plane.png
.. |arrow-keys| image:: ../../../../silx/resources/gui/icons/arrow-keys.png
+.. |axis| image:: ../../../../silx/resources/gui/icons/axis.png
.. |camera| image:: ../../../../silx/resources/gui/icons/camera.png
.. |clipboard| image:: ../../../../silx/resources/gui/icons/clipboard.png
.. |close| image:: ../../../../silx/resources/gui/icons/close.png
+.. |colorbar| image:: ../../../../silx/resources/gui/icons/colorbar.png
.. |colormap| image:: ../../../../silx/resources/gui/icons/colormap.png
.. |crop| image:: ../../../../silx/resources/gui/icons/crop.png
.. |crosshair| image:: ../../../../silx/resources/gui/icons/crosshair.png
@@ -275,23 +310,32 @@ Available icons
.. |item-2dim| image:: ../../../../silx/resources/gui/icons/item-2dim.png
.. |item-3dim| image:: ../../../../silx/resources/gui/icons/item-3dim.png
.. |item-ndim| image:: ../../../../silx/resources/gui/icons/item-ndim.png
+.. |item-none| image:: ../../../../silx/resources/gui/icons/item-none.png
.. |item-object| image:: ../../../../silx/resources/gui/icons/item-object.png
.. |last| image:: ../../../../silx/resources/gui/icons/last.png
+.. |math-amplitude| image:: ../../../../silx/resources/gui/icons/math-amplitude.png
.. |math-average| image:: ../../../../silx/resources/gui/icons/math-average.png
.. |math-derive| image:: ../../../../silx/resources/gui/icons/math-derive.png
.. |math-energy| image:: ../../../../silx/resources/gui/icons/math-energy.png
.. |math-fit| image:: ../../../../silx/resources/gui/icons/math-fit.png
+.. |math-imaginary| image:: ../../../../silx/resources/gui/icons/math-imaginary.png
.. |math-normalize| image:: ../../../../silx/resources/gui/icons/math-normalize.png
.. |math-peak-reset| image:: ../../../../silx/resources/gui/icons/math-peak-reset.png
.. |math-peak-search| image:: ../../../../silx/resources/gui/icons/math-peak-search.png
.. |math-peak| image:: ../../../../silx/resources/gui/icons/math-peak.png
+.. |math-phase-color-log| image:: ../../../../silx/resources/gui/icons/math-phase-color-log.png
+.. |math-phase-color| image:: ../../../../silx/resources/gui/icons/math-phase-color.png
+.. |math-phase| image:: ../../../../silx/resources/gui/icons/math-phase.png
+.. |math-real| image:: ../../../../silx/resources/gui/icons/math-real.png
.. |math-sigma| image:: ../../../../silx/resources/gui/icons/math-sigma.png
.. |math-smooth| image:: ../../../../silx/resources/gui/icons/math-smooth.png
.. |math-substract| image:: ../../../../silx/resources/gui/icons/math-substract.png
.. |math-swap-sign| image:: ../../../../silx/resources/gui/icons/math-swap-sign.png
.. |math-ymin-to-zero| image:: ../../../../silx/resources/gui/icons/math-ymin-to-zero.png
+.. |median-filter| image:: ../../../../silx/resources/gui/icons/median-filter.png
.. |next| image:: ../../../../silx/resources/gui/icons/next.png
.. |normal| image:: ../../../../silx/resources/gui/icons/normal.png
+.. |pan| image:: ../../../../silx/resources/gui/icons/pan.png
.. |pixel-intensities| image:: ../../../../silx/resources/gui/icons/pixel-intensities.png
.. |plot-grid| image:: ../../../../silx/resources/gui/icons/plot-grid.png
.. |plot-roi-above| image:: ../../../../silx/resources/gui/icons/plot-roi-above.png
@@ -310,9 +354,11 @@ Available icons
.. |plot-ylog| image:: ../../../../silx/resources/gui/icons/plot-ylog.png
.. |plot-yup| image:: ../../../../silx/resources/gui/icons/plot-yup.png
.. |previous| image:: ../../../../silx/resources/gui/icons/previous.png
+.. |profile-clear| image:: ../../../../silx/resources/gui/icons/profile-clear.png
.. |profile1D| image:: ../../../../silx/resources/gui/icons/profile1D.png
.. |profile2D| image:: ../../../../silx/resources/gui/icons/profile2D.png
.. |remove| image:: ../../../../silx/resources/gui/icons/remove.png
+.. |rotate-3d| image:: ../../../../silx/resources/gui/icons/rotate-3d.png
.. |rudder| image:: ../../../../silx/resources/gui/icons/rudder.png
.. |selected| image:: ../../../../silx/resources/gui/icons/selected.png
.. |shape-circle-solid| image:: ../../../../silx/resources/gui/icons/shape-circle-solid.png
@@ -329,17 +375,19 @@ Available icons
.. |sliders-off| image:: ../../../../silx/resources/gui/icons/sliders-off.png
.. |sliders-on| image:: ../../../../silx/resources/gui/icons/sliders-on.png
.. |spec| image:: ../../../../silx/resources/gui/icons/spec.png
-.. |test-png| image:: ../../../../silx/resources/gui/icons/test-png.png
.. |view-1d| image:: ../../../../silx/resources/gui/icons/view-1d.png
.. |view-2d-stack| image:: ../../../../silx/resources/gui/icons/view-2d-stack.png
.. |view-2d| image:: ../../../../silx/resources/gui/icons/view-2d.png
.. |view-3d| image:: ../../../../silx/resources/gui/icons/view-3d.png
.. |view-fullscreen| image:: ../../../../silx/resources/gui/icons/view-fullscreen.png
+.. |view-hdf5| image:: ../../../../silx/resources/gui/icons/view-hdf5.png
+.. |view-nexus| image:: ../../../../silx/resources/gui/icons/view-nexus.png
.. |view-nofullscreen| image:: ../../../../silx/resources/gui/icons/view-nofullscreen.png
.. |view-raw| image:: ../../../../silx/resources/gui/icons/view-raw.png
.. |view-refresh| image:: ../../../../silx/resources/gui/icons/view-refresh.png
.. |view-text| image:: ../../../../silx/resources/gui/icons/view-text.png
.. |window-new| image:: ../../../../silx/resources/gui/icons/window-new.png
+.. |zoom-back| image:: ../../../../silx/resources/gui/icons/zoom-back.png
.. |zoom-in| image:: ../../../../silx/resources/gui/icons/zoom-in.png
.. |zoom-original| image:: ../../../../silx/resources/gui/icons/zoom-original.png
.. |zoom-out| image:: ../../../../silx/resources/gui/icons/zoom-out.png
diff --git a/doc/source/modules/gui/img/IPythonDockWidget.png b/doc/source/modules/gui/img/IPythonDockWidget.png
new file mode 100644
index 0000000..111a926
--- /dev/null
+++ b/doc/source/modules/gui/img/IPythonDockWidget.png
Binary files differ
diff --git a/doc/source/modules/gui/img/IPythonWidget.png b/doc/source/modules/gui/img/IPythonWidget.png
new file mode 100644
index 0000000..18b44e0
--- /dev/null
+++ b/doc/source/modules/gui/img/IPythonWidget.png
Binary files differ
diff --git a/doc/source/modules/gui/index.rst b/doc/source/modules/gui/index.rst
index eecd124..0b7f433 100644
--- a/doc/source/modules/gui/index.rst
+++ b/doc/source/modules/gui/index.rst
@@ -19,3 +19,11 @@ The ``silx.gui`` package provides a set of PyQt widgets.
plot3d/index.rst
qt.rst
widgets/index.rst
+
+
+.. toctree::
+ :hidden:
+
+ gallery.rst
+
+See the :doc:`gallery` page for an overview of all available widgets.
diff --git a/doc/source/modules/gui/plot/actions/control.rst b/doc/source/modules/gui/plot/actions/control.rst
new file mode 100644
index 0000000..b35986d
--- /dev/null
+++ b/doc/source/modules/gui/plot/actions/control.rst
@@ -0,0 +1,5 @@
+:mod:`silx.gui.plot.actions.control`
+````````````````````````````````````
+
+.. automodule:: silx.gui.plot.actions.control
+ :members:
diff --git a/doc/source/modules/gui/plot/plotactions_examples.rst b/doc/source/modules/gui/plot/actions/examples.rst
index f14a42a..125e9c5 100644
--- a/doc/source/modules/gui/plot/plotactions_examples.rst
+++ b/doc/source/modules/gui/plot/actions/examples.rst
@@ -17,7 +17,7 @@ Simple example: Shift a curve
The following script is a simplistic example to show the basic required steps:
- - create a new class inheriting from :class:`silx.gui.plot.PlotActions.PlotAction`
+ - create a new class inheriting from :class:`silx.gui.plot.actions.PlotAction`
- define basic parameters such as the icon, the tooltip...
- write a method that will be triggered by the action
- initialize the new plot action by passing a reference to a plot window
@@ -27,7 +27,7 @@ The method implemented in this action interacts with the plot in a basic way. It
then it creates a new data array based on the curve data, and finally it replaces the original curve
with a new one using the modified data array.
-.. literalinclude:: ../../../../../examples/shiftPlotAction.py
+.. literalinclude:: ../../../../../../examples/shiftPlotAction.py
:lines: 36-
.. |imgShiftAction0| image:: img/shiftAction0.png
@@ -66,7 +66,7 @@ the amplitude spectrum. However, the inverse FFT requires the complete FFT data
We are therefore required to store the complex array of FFT data as curve metadata,
in order to be able to reverse the process when the action is unchecked.
-.. literalinclude:: ../../../../../examples/fftPlotAction.py
+.. literalinclude:: ../../../../../../examples/fftPlotAction.py
:lines: 44-
.. |imgFftAction0| image:: img/fftAction0.png
diff --git a/doc/source/modules/gui/plot/actions/fit.rst b/doc/source/modules/gui/plot/actions/fit.rst
new file mode 100644
index 0000000..8b06908
--- /dev/null
+++ b/doc/source/modules/gui/plot/actions/fit.rst
@@ -0,0 +1,5 @@
+:mod:`silx.gui.plot.actions.fit`
+````````````````````````````````
+
+.. automodule:: silx.gui.plot.actions.fit
+ :members:
diff --git a/doc/source/modules/gui/plot/actions/histogram.rst b/doc/source/modules/gui/plot/actions/histogram.rst
new file mode 100644
index 0000000..6a31236
--- /dev/null
+++ b/doc/source/modules/gui/plot/actions/histogram.rst
@@ -0,0 +1,5 @@
+:mod:`silx.gui.plot.actions.histogram`
+``````````````````````````````````````
+
+.. automodule:: silx.gui.plot.actions.histogram
+ :members:
diff --git a/doc/source/modules/gui/plot/img/fftAction0.png b/doc/source/modules/gui/plot/actions/img/fftAction0.png
index 5bd8061..5bd8061 100644
--- a/doc/source/modules/gui/plot/img/fftAction0.png
+++ b/doc/source/modules/gui/plot/actions/img/fftAction0.png
Binary files differ
diff --git a/doc/source/modules/gui/plot/img/fftAction1.png b/doc/source/modules/gui/plot/actions/img/fftAction1.png
index e4b20d7..e4b20d7 100644
--- a/doc/source/modules/gui/plot/img/fftAction1.png
+++ b/doc/source/modules/gui/plot/actions/img/fftAction1.png
Binary files differ
diff --git a/doc/source/modules/gui/plot/img/shiftAction0.png b/doc/source/modules/gui/plot/actions/img/shiftAction0.png
index 2d5837a..2d5837a 100644
--- a/doc/source/modules/gui/plot/img/shiftAction0.png
+++ b/doc/source/modules/gui/plot/actions/img/shiftAction0.png
Binary files differ
diff --git a/doc/source/modules/gui/plot/img/shiftAction3.png b/doc/source/modules/gui/plot/actions/img/shiftAction3.png
index a2187b8..a2187b8 100644
--- a/doc/source/modules/gui/plot/img/shiftAction3.png
+++ b/doc/source/modules/gui/plot/actions/img/shiftAction3.png
Binary files differ
diff --git a/doc/source/modules/gui/plot/actions/index.rst b/doc/source/modules/gui/plot/actions/index.rst
new file mode 100644
index 0000000..f95b1f8
--- /dev/null
+++ b/doc/source/modules/gui/plot/actions/index.rst
@@ -0,0 +1,30 @@
+.. currentmodule:: silx.gui.plot
+
+:mod:`actions`: Actions for PlotWidget
+===========================================
+
+.. currentmodule:: silx.gui.plot.actions
+
+The :class:`PlotAction` is a base class used to define plot actions.
+
+Plot actions serve to add menu items or toolbar items that are associated with a method
+that can interact with the associated :class:`.PlotWidget`.
+
+For an introduction to creating custom plot actions, see :doc:`examples`.
+
+actions API
+-----------
+
+Actions are divided into the following sub-modules:
+
+
+.. toctree::
+ :maxdepth: 1
+
+ control.rst
+ medfilt.rst
+ fit.rst
+ histogram.rst
+ io.rst
+
+.. autoclass:: silx.gui.plot.actions.PlotAction
diff --git a/doc/source/modules/gui/plot/actions/io.rst b/doc/source/modules/gui/plot/actions/io.rst
new file mode 100644
index 0000000..98f1808
--- /dev/null
+++ b/doc/source/modules/gui/plot/actions/io.rst
@@ -0,0 +1,6 @@
+:mod:`silx.gui.plot.actions.io`
+```````````````````````````````
+
+.. automodule:: silx.gui.plot.actions.io
+ :members:
+
diff --git a/doc/source/modules/gui/plot/actions/medfilt.rst b/doc/source/modules/gui/plot/actions/medfilt.rst
new file mode 100644
index 0000000..25f5af1
--- /dev/null
+++ b/doc/source/modules/gui/plot/actions/medfilt.rst
@@ -0,0 +1,5 @@
+:mod:`silx.gui.plot.actions.medfilt`
+````````````````````````````````````
+
+.. automodule:: silx.gui.plot.actions.medfilt
+ :members:
diff --git a/doc/source/modules/gui/plot/colormap.rst b/doc/source/modules/gui/plot/colormap.rst
new file mode 100644
index 0000000..a492605
--- /dev/null
+++ b/doc/source/modules/gui/plot/colormap.rst
@@ -0,0 +1,16 @@
+
+.. currentmodule:: silx.gui.plot.Colormap
+
+:mod:`Colormap`: Colormap API
+=============================
+
+.. automodule:: silx.gui.plot.Colormap
+
+.. currentmodule:: silx.gui.plot.Colormap
+
+:class:`Colormap` class
+-----------------------
+
+.. autoclass:: Colormap
+ :show-inheritance:
+ :members:
diff --git a/doc/source/modules/gui/plot/compleximageview.rst b/doc/source/modules/gui/plot/compleximageview.rst
new file mode 100644
index 0000000..d9009eb
--- /dev/null
+++ b/doc/source/modules/gui/plot/compleximageview.rst
@@ -0,0 +1,19 @@
+
+.. currentmodule:: silx.gui.plot
+
+:mod:`ComplexImageView`: Plot a 2D array of complex
+===================================================
+
+.. automodule:: silx.gui.plot.ComplexImageView
+
+.. currentmodule:: silx.gui.plot.ComplexImageView
+
+.. image:: img/ComplexImageView.png
+ :height: 400px
+ :align: center
+
+:class:`ComplexImageView` class
+-------------------------------
+
+.. autoclass:: ComplexImageView
+ :members:
diff --git a/doc/source/modules/gui/plot/dev.rst b/doc/source/modules/gui/plot/dev.rst
index 707d215..b793b01 100644
--- a/doc/source/modules/gui/plot/dev.rst
+++ b/doc/source/modules/gui/plot/dev.rst
@@ -10,7 +10,7 @@ This package is structured as follows.
:class:`PlotWidget` is a Qt widget (actually a :class:`QMainWindow`) displaying a 1D, 2D plot area.
It provides different interaction modes.
:class:`PlotWindow` is a Qt widget (actually a :class:`QMainWindow`) which adds a set of toolbar buttons and associated functionalities to :class:`PlotWidget`.
-The toolbar QActions are implemented in :mod:`.PlotActions`.
+The toolbar QActions are implemented in :mod:`.actions`.
:mod:`.Plot`, :mod:`.PlotEvents` and :mod:`.PlotInteraction` implement the plotting API regardless of the rendering backend and regardless of its integration in Qt.
The plotting API in defined in :mod:`.Plot`.
@@ -40,7 +40,6 @@ The :class:`PlotWindow` uses additional widgets:
The widgets also use the following miscellaneous modules:
- :mod:`.Colors` to convert colors from name to RGB(A)
-- :mod:`.MPLColormap` to embed recent matplotlib colormaps: 'magma', 'inferno', 'plasma' and 'viridis'.
- :mod:`._utils`: utility functions
The :mod:`backends` package provide the implementation of the rendering used by the :class:`Plot`.
@@ -89,15 +88,6 @@ The following modules are the modules used internally by the plot package.
.. automodule:: silx.gui.plot.backends.BackendMatplotlib
:members:
-:mod:`backends.ModestImage`
-+++++++++++++++++++++++++++
-
-.. currentmodule:: silx.gui.plot.backends.ModestImage
-
-.. automodule:: silx.gui.plot.backends.ModestImage
- :members:
- :undoc-members:
-
:mod:`ColormapDialog`
+++++++++++++++++++++
@@ -164,14 +154,6 @@ The following modules are the modules used internally by the plot package.
:members:
:show-inheritance:
-:mod:`MPLColormap`
-++++++++++++++++++
-
-.. currentmodule:: silx.gui.plot.MPLColormap
-
-.. automodule:: silx.gui.plot.MPLColormap
- :members:
-
:mod:`PlotEvents`
+++++++++++++++++
diff --git a/doc/source/modules/gui/plot/getting_started.rst b/doc/source/modules/gui/plot/getting_started.rst
index 9e45b70..484d3f7 100644
--- a/doc/source/modules/gui/plot/getting_started.rst
+++ b/doc/source/modules/gui/plot/getting_started.rst
@@ -157,6 +157,7 @@ a module from :mod:`silx.gui`:
.. warning::
+
:mod:`silx.gui.plot` widgets are not thread-safe.
All calls to :mod:`silx.gui.plot` widgets must be made from the main thread.
@@ -342,17 +343,15 @@ When updating an image, if ``origin`` and ``scale`` are not provided, the previo
Colormap
++++++++
-A ``colormap`` is described with a :class:`dict` as follows (See :mod:`silx.gui.plot.Plot` for full documentation of the colormap):
+A ``colormap`` is described with a :class:`.Colormap` class as follows:
.. code-block:: python
- colormap = {
- 'name': 'gray', # Name of the colormap
- 'normalization': 'linear', # Either 'linear' or 'log'
- 'autoscale': True, # True to autoscale colormap to data range, False to use [vmin, vmax]
- 'vmin': 0.0, # If not autoscale, data value to bind to min of colormap
- 'vmax': 1.0 # If not autoscale, data value to bind to max of colormap
- }
+ colormap = Colormap(name='gray', # Name of the colormap
+ normalization='linear', # Either 'linear' or 'log'
+ vmin=0.0, # If not autoscale, data value to bind to min of colormap
+ vmax=1.0 # If not autoscale, data value to bind to max of colormap
+ )
At least the following colormap names are guaranteed to be available, but any colormap name from `matplotlib <http://matplotlib.org/>`_ (see `Choosing Colormaps <http://matplotlib.org/users/colormaps.html>`_) should work:
@@ -372,19 +371,23 @@ It is possible to change the default colormap of :meth:`.PlotWidget.addImage` fo
.. code-block:: python
- colormap = {'name': 'viridis', 'normalization': 'linear',
- 'autoscale': True, 'vmin': 0.0, 'vmax': 1.0}
+ colormap = Colormap(name='viridis',
+ normalization='linear',
+ vmin=0.0,
+ vmax=1.0)
plot.setDefaultColormap(colormap)
data = numpy.arange(512 * 512.).reshape(512, -1)
plot.addImage(data) # Rendered with the default colormap set before
-It is also possible to provide a ``colormap`` to :meth:`.PlotWidget.addImage` to override this default for an image:
+It is also possible to provide a :class:`.Colormap` to :meth:`.PlotWidget.addImage` to override this default for an image:
.. code-block:: python
- colormap = {'name': 'magma', 'normalization': 'log',
- 'autoscale': False, 'vmin': 1.2, 'vmax': 1.8}
+ colormap = Colormap(name='magma',
+ normalization='log',
+ vmin=1.2,
+ vmax=1.8)
data = numpy.random.random(512 * 512).reshape(512, -1) + 1.
plot.addImage(data, colormap=colormap)
@@ -432,18 +435,19 @@ Control plot axes
-----------------
The following examples illustrate the API to control the plot axes.
+:meth:`.PlotWidget.getXAxis` and :meth:`.PlotWidget.getYAxis` give access to each plot axis (:class:`.items.Axis`) in order to control them.
Labels and title
++++++++++++++++
Use :meth:`.PlotWidget.setGraphTitle` to set the plot main title.
-Use :meth:`.PlotWidget.setGraphXLabel` and :meth:`.PlotWidget.setGraphYLabel` to set the axes text labels:
+Use :meth:`.PlotWidget.getXAxis` and :meth:`.PlotWidget.getYAxis` to get the axes and set their text label with :meth:`.items.Axis.setLabel`:
.. code-block:: python
plot.setGraphTitle('My plot')
- plot.setGraphXLabel('X')
- plot.setGraphYLabel('Y')
+ plot.getXAxis().setLabel('X')
+ plot.getYAxis().setLabel('Y')
Axes limits
@@ -455,9 +459,9 @@ The following code moves the visible plot area to the right:
.. code-block:: python
- xmin, xmax = plot.getGraphXLimits()
+ xmin, xmax = plot.getXAxis().getLimits()
offset = 0.1 * (xmax - xmin)
- plot.setGraphXLimits(xmin + offset, xmax + offset)
+ plot.getXAxis().setLimits(xmin + offset, xmax + offset)
:meth:`.PlotWidget.resetZoom` set the plot limits to the bounds of the data:
@@ -465,7 +469,7 @@ The following code moves the visible plot area to the right:
plot.resetZoom()
-See :meth:`.PlotWidget.resetZoom`, :meth:`.PlotWidget.setLimits`, :meth:`.PlotWidget.getGraphXLimits`, :meth:`.PlotWidget.setGraphXLimits`, :meth:`.PlotWidget.getGraphYLimits`, :meth:`.PlotWidget.setGraphYLimits` for details.
+See :meth:`.PlotWidget.resetZoom`, :meth:`.PlotWidget.setLimits`, :meth:`.PlotWidget.getXAxis`, :meth:`.PlotWidget.getYAxis` and :class:`.items.Axis` for details.
Axes
@@ -475,17 +479,17 @@ Different methods allow plot axes modifications:
.. code-block:: python
- plot.setYAxisInverted(True) # Makes the Y axis pointing downward
+ plot.getYAxis().setInverted(True) # Makes the Y axis pointing downward
plot.setKeepDataAspectRatio(True) # To keep aspect ratio between X and Y axes
-See :meth:`.PlotWidget.setYAxisInverted`, :meth:`.PlotWidget.setKeepDataAspectRatio` for details.
+See :meth:`.PlotWidget.getYAxis`, :meth:`.PlotWidget.setKeepDataAspectRatio` for details.
.. code-block:: python
plot.setGraphGrid(which='both') # To show a grid for both minor and major axes ticks
# Use logarithmic axes
- plot.setXAxisLogarithmic(True)
- plot.setYAxisLogarithmic(True)
+ plot.getXAxis().setScale("log")
+ plot.getYAxis().setScale("log")
-See :meth:`.PlotWidget.setGraphGrid`, :meth:`.PlotWidget.setXAxisLogarithmic`, :meth:`.PlotWidget.setYAxisLogarithmic` for details.
+See :meth:`.PlotWidget.setGraphGrid`, :meth:`.PlotWidget.getXAxis`, :meth:`.PlotWidget.getXAxis` and :class:`.items.Axis` for details.
diff --git a/doc/source/modules/gui/plot/imageview.rst b/doc/source/modules/gui/plot/imageview.rst
index c2663c7..f03868b 100644
--- a/doc/source/modules/gui/plot/imageview.rst
+++ b/doc/source/modules/gui/plot/imageview.rst
@@ -6,10 +6,12 @@
.. automodule:: silx.gui.plot.ImageView
-.. currentmodule:: silx.gui.plot.ImageView
+.. image:: img/ImageView.png
+ :height: 400
+ :align: center
:class:`ImageView` class
------------------------
.. autoclass:: ImageView
- :members: valueChanged, profile, setImage, getColormap, setColormap, getHistogram, setGraphTitle, setGraphXLabel, setGraphYLabel
+ :members: valueChanged, setImage, getColormap, setColormap, getHistogram, setGraphTitle, getXAxis, getYAxis
diff --git a/doc/source/modules/gui/plot/img/ComplexImageView.png b/doc/source/modules/gui/plot/img/ComplexImageView.png
new file mode 100644
index 0000000..3f8b502
--- /dev/null
+++ b/doc/source/modules/gui/plot/img/ComplexImageView.png
Binary files differ
diff --git a/doc/source/modules/gui/plot/img/printPreviewMultiPlot.png b/doc/source/modules/gui/plot/img/printPreviewMultiPlot.png
new file mode 100644
index 0000000..4ce42b3
--- /dev/null
+++ b/doc/source/modules/gui/plot/img/printPreviewMultiPlot.png
Binary files differ
diff --git a/doc/source/modules/gui/plot/index.rst b/doc/source/modules/gui/plot/index.rst
index 143b85b..b77bbcc 100644
--- a/doc/source/modules/gui/plot/index.rst
+++ b/doc/source/modules/gui/plot/index.rst
@@ -15,94 +15,12 @@
For an introduction to the widgets of this package, see :doc:`getting_started`.
-For examples of custom plot actions, see :doc:`plotactions_examples`.
+For examples of custom plot actions, see :doc:`actions/examples`.
Widgets gallery
---------------
-The :mod:`silx.gui.plot` package provides the following plotting widgets:
-
-.. |imgPlotWidget| image:: img/PlotWidget.png
- :height: 150px
- :align: middle
-
-.. |imgPlotWindow| image:: img/PlotWindow.png
- :height: 150px
- :align: middle
-
-.. |imgPlot1D| image:: img/Plot1D.png
- :height: 150px
- :align: middle
-
-.. |imgPlot2D| image:: img/Plot2D.png
- :height: 150px
- :align: middle
-
-.. |imgImageView| image:: img/ImageView.png
- :height: 150px
- :align: middle
-
-.. |imgStackView| image:: img/StackView.png
- :height: 150px
- :align: middle
-
-.. list-table::
- :widths: 1 4
- :header-rows: 1
-
- * - Widget
- - Description
- * - |imgPlotWidget|
- - :class:`PlotWidget` is the base Qt widget providing a plot area.
- Other plot widgets are based on this one and provides the same API.
- * - |imgPlotWindow|
- - :class:`PlotWindow` adds a toolbar to :class:`PlotWidget`.
- The content of this toolbar can be configured from the
- :class:`PlotWindow` constructor or by hiding its content afterward.
- * - |imgPlot1D|
- - :class:`.Plot1D` is a :class:`PlotWindow` configured with tools useful
- for curves.
- * - |imgPlot2D|
- - :class:`.Plot2D` is a :class:`PlotWindow` configured with tools useful
- for images.
- * - |imgImageView|
- - :class:`ImageView` adds side histograms to a :class:`.Plot2D` widget.
- * - |imgStackView|
- - :class:`StackView` is a widget designed to display an image from a
- stack of images in a :class:`PlotWindow` widget, with a frame browser
- to navigate in the stack. The profile tool can do a 2D profile on the
- stack of images.
-
-It also provides (and uses) widgets that can be attached to a :class:`PlotWidget`:
-
-.. |imgPositionInfo| image:: img/PositionInfo.png
- :width: 300px
- :align: middle
-
-.. |imgLimitsToolBar| image:: img/LimitsToolBar.png
- :width: 300px
- :align: middle
-
-.. |imgColorbar| image:: img/logColorbar.png
- :height: 150px
- :align: middle
-
-
-.. list-table::
- :widths: 1 4
- :header-rows: 1
-
- * - Widget
- - Description
- * - |imgPositionInfo|
- - :class:`.PlotTools.PositionInfo` is a widget displaying mouse position and
- information of a :class:`PlotWidget` associated to the mouse position.
- * - |imgLimitsToolBar|
- - :class:`.PlotTools.LimitsToolBar` is a QToolBar displaying and
- controlling the limits of a :class:`PlotWidget`.
- * - |imgColorbar|
- - :class:`.ColorBar.ColorBarWidget` display colormap gradient and can be linked with a plot
- to display the colormap
+See :ref:`plot-gallery` gallery.
Public modules
--------------
@@ -114,12 +32,14 @@ Public modules
plotwindow.rst
imageview.rst
stackview.rst
- plot.rst
+ compleximageview.rst
+ colormap.rst
items.rst
- plotactions.rst
+ actions/index.rst
plottools.rst
profile.rst
roi.rst
+ printpreviewtoolbutton.rst
Internals
---------
diff --git a/doc/source/modules/gui/plot/items.rst b/doc/source/modules/gui/plot/items.rst
index beba3a1..3cdcf17 100644
--- a/doc/source/modules/gui/plot/items.rst
+++ b/doc/source/modules/gui/plot/items.rst
@@ -8,56 +8,103 @@
.. currentmodule:: silx.gui.plot.items
+Item
+----
+
+All plot primitives inherits from :class:`Item` as a common ground:
+
+.. autoclass:: Item
+ :show-inheritance:
+ :members:
+
Curve
-----
.. autoclass:: Curve
- :members:
- :inherited-members:
+ :members: getData, getXData, getYData, getXErrorData, getYErrorData, setData,
+ getSymbol, setSymbol, getSymbolSize, setSymbolSize,
+ getAlpha, setAlpha,
+ getColor, setColor,
+ getYAxis, setYAxis,
+ isFill, setFill,
+ getXLabel, getYLabel,
+ getLineWidth, setLineWidth, getLineStyle, setLineStyle,
+ isHighlighted, setHighlighted, getHighlightedColor, setHighlightedColor,
+ getCurrentColor
Images
------
.. autoclass:: ImageData
- :members:
- :inherited-members:
+ :members: getData, getRgbaImageData,
+ getOrigin, setOrigin,
+ getScale, setScale,
+ isDraggable,
+ getAlpha, setAlpha,
+ getColormap, setColormap,
+ getAlternativeImageData
.. autoclass:: ImageRgba
- :members:
- :inherited-members:
+ :members: getData, getRgbaImageData,
+ getOrigin, setOrigin,
+ getScale, setScale,
+ isDraggable,
+ getAlpha, setAlpha
Scatter
-------
.. autoclass:: Scatter
- :members:
- :inherited-members:
+ :members: getValueData,
+ getData, getXData, getYData, getXErrorData, getYErrorData, setData,
+ getSymbol, setSymbol, getSymbolSize, setSymbolSize,
+ getAlpha, setAlpha,
+ getColormap, setColormap
Histogram
---------
.. autoclass:: Histogram
- :members:
- :inherited-members:
+ :members: getValueData, getBinEdgesData, getData, setData,
+ getAlpha, setAlpha,
+ getColor, setColor,
+ getYAxis, setYAxis,
+ isFill, setFill,
+ getLineWidth, setLineWidth, getLineStyle, setLineStyle
Markers
-------
.. autoclass:: Marker
- :members:
- :inherited-members:
+ :members: getText, setText, getXPosition, getYPosition, getPosition, setPosition, getConstraint,
+ getSymbol, setSymbol, getSymbolSize, setSymbolSize
.. autoclass:: XMarker
- :members:
- :inherited-members:
+ :members: getText, setText, getXPosition, getYPosition, getPosition, setPosition, getConstraint
.. autoclass:: YMarker
+ :members: getText, setText, getXPosition, getYPosition, getPosition, setPosition, getConstraint
+
+Shapes
+------
+
+.. autoclass:: Shape
+ :members: setOverlay,
+ getColor, setColor,
+ isFill, setFill,
+ getType, getPoints, setPoints
+
+Item changed signal
+-------------------
+
+Plot items emit a :attr:`Item.sigItemChanged` signal when their values are updated.
+This signal provides a flag in the following enumeration describing the modified value:
+
+.. autoclass:: ItemChangedType
:members:
- :inherited-members:
-Item
+Axis
----
-.. autoclass:: Shape
+.. autoclass:: Axis
:members:
- :inherited-members:
diff --git a/doc/source/modules/gui/plot/plot.rst b/doc/source/modules/gui/plot/plot.rst
deleted file mode 100644
index 2b8e096..0000000
--- a/doc/source/modules/gui/plot/plot.rst
+++ /dev/null
@@ -1,16 +0,0 @@
-
-.. currentmodule:: silx.gui.plot
-
-:mod:`Plot`: Full plot API
-==========================
-
-.. automodule:: silx.gui.plot.Plot
-
-.. currentmodule:: silx.gui.plot.Plot
-
-:class:`Plot` class
--------------------
-
-.. autoclass:: Plot
- :show-inheritance:
- :members:
diff --git a/doc/source/modules/gui/plot/plotactions.rst b/doc/source/modules/gui/plot/plotactions.rst
deleted file mode 100644
index 29a3656..0000000
--- a/doc/source/modules/gui/plot/plotactions.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-
-.. currentmodule:: silx.gui.plot
-
-:mod:`PlotActions`: Actions for PlotWidget
-===========================================
-
-.. currentmodule:: silx.gui.plot.PlotActions
-
-The :class:`PlotAction` is a base class used to define plot actions.
-
-Plot actions serve to add menu items or toolbar items that are associated with a method
-that can interact with the associated :class:`PlotWidget`.
-
-For an introduction to creating custom plot actions, see :doc:`plotactions_examples`.
-
-PlotActions API
----------------
-
-.. automodule:: silx.gui.plot.PlotActions
- :members:
diff --git a/doc/source/modules/gui/plot/plotwidget.rst b/doc/source/modules/gui/plot/plotwidget.rst
index df576fc..a8e7831 100644
--- a/doc/source/modules/gui/plot/plotwidget.rst
+++ b/doc/source/modules/gui/plot/plotwidget.rst
@@ -14,13 +14,8 @@ It is the basis of other plot widget, thus all plot widgets share the same API.
For an introduction and examples of the plot API, see :doc:`getting_started`.
-Plot API
---------
-
-.. currentmodule:: silx.gui.plot.Plot
-
-This is a choosen subset of the complete plot API, the full API is
-documented in :class:`silx.gui.plot.Plot`.
+:class:`PlotWidget`
+-------------------
.. currentmodule:: silx.gui.plot.PlotWidget
@@ -37,6 +32,20 @@ Those methods allow to add and update plotted data:
.. automethod:: PlotWidget.addScatter
.. automethod:: PlotWidget.addHistogram
+Get data
+........
+
+Those methods return objects providing access to plotted data:
+
+.. automethod:: PlotWidget.getCurve
+.. automethod:: PlotWidget.getImage
+.. automethod:: PlotWidget.getScatter
+.. automethod:: PlotWidget.getHistogram
+
+.. automethod:: PlotWidget.getAllCurves
+.. automethod:: PlotWidget.getAllImages
+
+
Plot markers
............
@@ -52,59 +61,40 @@ Remove data from the plot
.. automethod:: PlotWidget.clear
.. automethod:: PlotWidget.remove
-Title and labels
-................
+Title
+.....
-Those methods handle the text labels of the axes and the plot title:
+Those methods handle the plot title:
.. automethod:: PlotWidget.getGraphTitle
.. automethod:: PlotWidget.setGraphTitle
-.. automethod:: PlotWidget.getGraphXLabel
-.. automethod:: PlotWidget.setGraphXLabel
-.. automethod:: PlotWidget.getGraphYLabel
-.. automethod:: PlotWidget.setGraphYLabel
-
-Axes limits
-...........
-
-Those methods change the range of data values displayed on each axis.
-
-.. automethod:: PlotWidget.getGraphXLimits
-.. automethod:: PlotWidget.setGraphXLimits
-.. automethod:: PlotWidget.getGraphYLimits
-.. automethod:: PlotWidget.setGraphYLimits
-.. automethod:: PlotWidget.setLimits
Axes
....
-The following methods handle the display properties of the axes:
+Those two methods give access to :class:`.items.Axis` which handle the limits, scales and labels of axis:
-.. automethod:: PlotWidget.isXAxisLogarithmic
-.. automethod:: PlotWidget.setXAxisLogarithmic
-.. automethod:: PlotWidget.isYAxisLogarithmic
-.. automethod:: PlotWidget.setYAxisLogarithmic
+.. automethod:: PlotWidget.getXAxis
+.. automethod:: PlotWidget.getYAxis
-.. automethod:: PlotWidget.isYAxisInverted
-.. automethod:: PlotWidget.setYAxisInverted
+The following methods handle plot limits, aspect ratio, grid and axes display:
+
+.. automethod:: PlotWidget.setLimits
.. automethod:: PlotWidget.isKeepDataAspectRatio
.. automethod:: PlotWidget.setKeepDataAspectRatio
.. automethod:: PlotWidget.getGraphGrid
.. automethod:: PlotWidget.setGraphGrid
+.. automethod:: PlotWidget.setAxesDisplayed
Reset zoom
..........
.. automethod:: PlotWidget.resetZoom
-Those methods change the behavior of :meth:`PlotWidget.resetZoom`.
+The following methods allow to add margins around the data when performing a zoom reset:
.. automethod:: PlotWidget.getDataMargins
.. automethod:: PlotWidget.setDataMargins
-.. automethod:: PlotWidget.isXAxisAutoScale
-.. automethod:: PlotWidget.setXAxisAutoScale
-.. automethod:: PlotWidget.isYAxisAutoScale
-.. automethod:: PlotWidget.setYAxisAutoScale
Defaults
........
@@ -130,10 +120,35 @@ of the plot and to toggle the use of a crosshair cursor:
.. automethod:: PlotWidget.getGraphCursor
.. automethod:: PlotWidget.setGraphCursor
+.. automethod:: PlotWidget.isPanWithArrowKeys
+.. automethod:: PlotWidget.setPanWithArrowKeys
+
+Coordinates conversion
+......................
+
+.. automethod:: PlotWidget.getDataRange
+.. automethod:: PlotWidget.getPlotBoundsInPixels
+.. automethod:: PlotWidget.dataToPixel
+.. automethod:: PlotWidget.pixelToData
+
+Active Item
+...........
+
+.. automethod:: PlotWidget.isActiveCurveHandling
+.. automethod:: PlotWidget.setActiveCurveHandling
+.. automethod:: PlotWidget.getActiveCurveColor
+.. automethod:: PlotWidget.setActiveCurveColor
+.. automethod:: PlotWidget.getActiveCurve
+.. automethod:: PlotWidget.setActiveCurve
+.. automethod:: PlotWidget.getActiveImage
+.. automethod:: PlotWidget.setActiveImage
+
Misc.
.....
+.. automethod:: PlotWidget.getWidgetHandle
.. automethod:: PlotWidget.saveGraph
+.. automethod:: PlotWidget.setDefaultBackend
Signals
.......
@@ -141,11 +156,6 @@ Signals
The :class:`PlotWidget` provides the following Qt signals:
.. autoattribute:: PlotWidget.sigPlotSignal
-.. autoattribute:: PlotWidget.sigSetYAxisInverted
-.. autoattribute:: PlotWidget.sigSetXAxisLogarithmic
-.. autoattribute:: PlotWidget.sigSetYAxisLogarithmic
-.. autoattribute:: PlotWidget.sigSetXAxisAutoScale
-.. autoattribute:: PlotWidget.sigSetYAxisAutoScale
.. autoattribute:: PlotWidget.sigSetKeepDataAspectRatio
.. autoattribute:: PlotWidget.sigSetGraphGrid
.. autoattribute:: PlotWidget.sigSetGraphCursor
@@ -153,21 +163,33 @@ The :class:`PlotWidget` provides the following Qt signals:
.. autoattribute:: PlotWidget.sigContentChanged
.. autoattribute:: PlotWidget.sigActiveCurveChanged
.. autoattribute:: PlotWidget.sigActiveImageChanged
+.. autoattribute:: PlotWidget.sigActiveScatterChanged
.. autoattribute:: PlotWidget.sigInteractiveModeChanged
-.. Not documented:
- addItem, removeItem, clearItems
- isActiveCurveHandling, enableActiveCurveHandling,
- getActiveCurveColor, setActiveCurveColor,
- getActiveCurve, setActiveCurve,
- isCurveHidden, hideCurve,
- getActiveImage, setActiveImage,
- getAllCurves, getCurve, getImage, getAllImages,
- getScatter, getHistogram,
- setDefaultPlotPoints, setDefaultPlotLines,
- getWidgetHandle, notify, setCallback, graphCallback,
- dataToPixel, pixelToData, getPlotBoundsInPixels,
- setGraphCursorShape, pickMarker, moveMarker, pickImageOrCurve, moveImage,
- onMousePress, onMouseMove, onMouseRelease, onMouseWheel,
- isDrawModeEnabled, setDrawModeEnabled, getDrawMode,
- isZoomModeEnabled, setZoomModeEnabled,
+.. PlotWidget public API that is not documented:
+ Could be added:
+ - addItem
+ - pan
+ - getLimitsHistory
+ - isDefaultPlotPoints
+ - isDefaultPlotLines
+ - setGraphCursorShape
+ - getAutoReplot, setAutoReplot, replot
+ Should not be added:
+ * Should be private:
+ - notify, setCallback, graphCallback
+ * Use remove instead:
+ - removeCurve, removeImage, removeItem, removeMarker
+ - clearCurves, clearImages, clearItems, clearMarkers
+ * Use items instead:
+ - isCurveHidden, hideCurve
+ * Use items.axis instead:
+ - getGraphXLimits, setGraphXLimits
+ - getGraphYLimits, setGraphYLimits
+ - getGraphXLabel, setGraphXLabel
+ - getGraphYLabel, setGraphYLabel
+ - isXAxisLogarithmic, setXAxisLogarithmic
+ - isYAxisLogarithmic, setXAxisLogarithmic
+ - isXAxisAutoScale, setXAxisAutoScale
+ - isYAxisAutoScale, setYAxisAutoScale
+ - setYAxisInverted, isYAxisInverted
diff --git a/doc/source/modules/gui/plot/printpreviewtoolbutton.rst b/doc/source/modules/gui/plot/printpreviewtoolbutton.rst
new file mode 100644
index 0000000..42b3832
--- /dev/null
+++ b/doc/source/modules/gui/plot/printpreviewtoolbutton.rst
@@ -0,0 +1,8 @@
+
+.. currentmodule:: silx.gui.plot
+
+:mod:`PrintPreviewToolButton`: Print preview buttons
+====================================================
+
+.. automodule:: silx.gui.plot.PrintPreviewToolButton
+ :members:
diff --git a/doc/source/modules/gui/plot/stackview.rst b/doc/source/modules/gui/plot/stackview.rst
index 7f2442a..11c7828 100644
--- a/doc/source/modules/gui/plot/stackview.rst
+++ b/doc/source/modules/gui/plot/stackview.rst
@@ -13,7 +13,9 @@
.. autoclass:: StackView
:members:
- :exclude-members: remove, setInteractiveMode, addItem
+ :exclude-members: remove, setInteractiveMode, addItem, getActiveImage,
+ resetZoom, setYAxisInverted, isYAxisInverted, getSupportedColormaps,
+ isKeepDataAspectRatio, setKeepDataAspectRatio
:class:`StackViewMainWindow` class
----------------------------------
diff --git a/doc/source/modules/gui/plot3d/actions.rst b/doc/source/modules/gui/plot3d/actions.rst
index a2fde68..c9e3af2 100644
--- a/doc/source/modules/gui/plot3d/actions.rst
+++ b/doc/source/modules/gui/plot3d/actions.rst
@@ -1,10 +1,15 @@
-Actions
-=======
+:mod:`actions`
+==============
-:mod:`Plot3DActions`
---------------------
+.. currentmodule:: silx.gui.plot3d.actions
-.. currentmodule:: silx.gui.plot3d.Plot3DActions
+.. automodule:: silx.gui.plot3d.actions
+ :members:
+
+.. automodule:: silx.gui.plot3d.actions.io
+ :members:
-.. automodule:: silx.gui.plot3d.Plot3DActions
+.. automodule:: silx.gui.plot3d.actions.mode
:members:
+
+.. autoclass:: Plot3DAction
diff --git a/doc/source/modules/gui/plot3d/dev.rst b/doc/source/modules/gui/plot3d/dev.rst
index 757240e..96704f8 100644
--- a/doc/source/modules/gui/plot3d/dev.rst
+++ b/doc/source/modules/gui/plot3d/dev.rst
@@ -12,10 +12,10 @@ Widget-level API
Widgets are available as modules of the :mod:`silx.gui.plot3d` packages.
The :mod:`.Plot3DWidget` module provides the OpenGL canvas where the scene is rendered.
-The :mod:`.Plot3DWindow` module provides a :class:`QMainWindow` with a :class:`Plot3DWindow` as its central widget
-and toolbars: :class:`ViewpointToolBar` and :class:`Plot3DToolBar`.
-:class:`QAction` that can be associated with a :class:`Plot3DWidget` are defined in the :mod:`.Plot3DActions` module.
-Those actions are used by the :class:`Plot3DToolBar` toolbar.
+The :mod:`.Plot3DWindow` module provides a :class:`QMainWindow` with a :class:`Plot3DWindow` as its central widget,
+toolbars (:class:`InteractiveModeToolBar` and :class:`OutputToolBar`) and a :class:`ViewpointToolButton` in a toolbar.
+:class:`QAction` that can be associated with a :class:`Plot3DWidget` are defined in the :mod:`.actions` module.
+Those actions are used by the :class:`OutputToolBar` and the :class:`InteractiveModeToolBar` toolbars.
The :mod:`.ScalarFieldView` module defines the :class:`ScalarFieldView` widget that displays iso-surfaces of a 3D scalar data set and the associated classes.
The :mod:`.SFViewParamTree` module defines a :class:`SFViewParamTree.TreeView` widget that can be attached to a :class:`ScalarFieldView` to control the display.
diff --git a/doc/source/modules/gui/plot3d/index.rst b/doc/source/modules/gui/plot3d/index.rst
index 8941403..38b8f02 100644
--- a/doc/source/modules/gui/plot3d/index.rst
+++ b/doc/source/modules/gui/plot3d/index.rst
@@ -11,42 +11,7 @@
Widgets gallery
---------------
-.. |imgPlot3DWidget| image:: img/Plot3DWidget.png
- :height: 150px
- :align: middle
-
-.. |imgPlot3DWindow| image:: img/Plot3DWindow.png
- :height: 150px
- :align: middle
-
-.. |imgScalarFieldView| image:: img/ScalarFieldView.png
- :height: 150px
- :align: middle
-
-.. |imgSFViewParamTree| image:: img/SFViewParamTree.png
- :height: 150px
- :align: middle
-
-.. list-table::
- :widths: 1 4
- :header-rows: 1
-
- * - Widget
- - Description
- * - |imgScalarFieldView|
- - :class:`ScalarFieldView` is a :class:`Plot3DWindow` dedicated to display 3D scalar field.
- It can display iso-surfaces and an interactive cutting plane.
- Sample code: :doc:`viewer3dvolume_example`.
- * - |imgPlot3DWindow|
- - :class:`Plot3DWindow` is a :class:`QMainWindow` with a :class:`Plot3DWidget` as central widget
- and toolbars.
- * - |imgPlot3DWidget|
- - :class:`Plot3DWidget` is the base Qt widget providing an OpenGL 3D scene.
- Other widgets are using this widget as the OpenGL scene canvas.
- * - |imgSFViewParamTree|
- - :class:`SFViewParamTree` is a :class:`QTreeView` widget that can be attached to a :class:`ScalarFieldView`.
- It displays current parameters of the :class:`ScalarFieldView` and allows to modify it.
- Sample code: :doc:`viewer3dvolume_example`.
+See :ref:`plot3d-gallery` gallery.
Public modules
--------------
@@ -60,7 +25,7 @@ The following sub-modules are available:
plot3dwindow.rst
scalarfieldview.rst
sfviewparamtree.rst
- toolbars.rst
+ tools.rst
actions.rst
diff --git a/doc/source/modules/gui/plot3d/scalarfieldview.rst b/doc/source/modules/gui/plot3d/scalarfieldview.rst
index 763c18e..80127ea 100644
--- a/doc/source/modules/gui/plot3d/scalarfieldview.rst
+++ b/doc/source/modules/gui/plot3d/scalarfieldview.rst
@@ -21,13 +21,6 @@ Helper classes
Those classes are used by :class:`ScalarFieldView`.
-:class:`Colormap`
-+++++++++++++++++
-
-.. autoclass:: Colormap
- :show-inheritance:
- :members:
-
:class:`CutPlane`
+++++++++++++++++
diff --git a/doc/source/modules/gui/plot3d/toolbars.rst b/doc/source/modules/gui/plot3d/toolbars.rst
deleted file mode 100644
index df4b4ef..0000000
--- a/doc/source/modules/gui/plot3d/toolbars.rst
+++ /dev/null
@@ -1,29 +0,0 @@
-Toolbars
-========
-
-
-:mod:`ViewpointToolbar`
------------------------
-
-.. currentmodule:: silx.gui.plot3d.ViewpointToolBar
-
-.. automodule:: silx.gui.plot3d.ViewpointToolBar
-
-.. autoclass:: ViewpointActionGroup
- :show-inheritance:
- :members:
-
-.. autoclass:: ViewpointToolBar
- :show-inheritance:
- :members:
-
-:mod:`Plot3DToolbar`
---------------------
-
-.. currentmodule:: silx.gui.plot3d.Plot3DToolBar
-
-.. automodule:: silx.gui.plot3d.Plot3DToolBar
-
-.. autoclass:: Plot3DToolBar
- :show-inheritance:
- :members:
diff --git a/doc/source/modules/gui/plot3d/tools.rst b/doc/source/modules/gui/plot3d/tools.rst
new file mode 100644
index 0000000..6cd433b
--- /dev/null
+++ b/doc/source/modules/gui/plot3d/tools.rst
@@ -0,0 +1,40 @@
+:mod:`tools`
+============
+
+.. currentmodule:: silx.gui.plot3d.tools
+
+.. automodule:: silx.gui.plot3d.tools
+
+Toolbars
+--------
+
+:class:`InteractiveModeToolbar`
++++++++++++++++++++++++++++++++
+
+.. autoclass:: InteractiveModeToolBar
+ :show-inheritance:
+ :members:
+
+:class:`OutputToolBar`
+++++++++++++++++++++++
+
+.. autoclass:: OutputToolBar
+ :show-inheritance:
+ :members:
+
+:class:`ViewpointToolbar`
++++++++++++++++++++++++++
+
+.. autoclass:: ViewpointToolBar
+ :show-inheritance:
+ :members:
+
+Tool Buttons
+------------
+
+:class:`ViewpointToolButton`
+++++++++++++++++++++++++++++
+
+.. autoclass:: ViewpointToolButton
+ :show-inheritance:
+ :members:
diff --git a/doc/source/modules/gui/widgets/img/FrameBrowser.png b/doc/source/modules/gui/widgets/img/FrameBrowser.png
new file mode 100644
index 0000000..c5624f7
--- /dev/null
+++ b/doc/source/modules/gui/widgets/img/FrameBrowser.png
Binary files differ
diff --git a/doc/source/modules/gui/widgets/img/HorizontalSliderWithBrowser.png b/doc/source/modules/gui/widgets/img/HorizontalSliderWithBrowser.png
new file mode 100644
index 0000000..d64b2df
--- /dev/null
+++ b/doc/source/modules/gui/widgets/img/HorizontalSliderWithBrowser.png
Binary files differ
diff --git a/doc/source/modules/gui/widgets/img/PeriodicCombo.png b/doc/source/modules/gui/widgets/img/PeriodicCombo.png
new file mode 100644
index 0000000..e0b40c2
--- /dev/null
+++ b/doc/source/modules/gui/widgets/img/PeriodicCombo.png
Binary files differ
diff --git a/doc/source/modules/gui/widgets/img/PeriodicList.png b/doc/source/modules/gui/widgets/img/PeriodicList.png
new file mode 100644
index 0000000..d1e540b
--- /dev/null
+++ b/doc/source/modules/gui/widgets/img/PeriodicList.png
Binary files differ
diff --git a/doc/source/modules/gui/widgets/img/PeriodicTable.png b/doc/source/modules/gui/widgets/img/PeriodicTable.png
new file mode 100644
index 0000000..c06dded
--- /dev/null
+++ b/doc/source/modules/gui/widgets/img/PeriodicTable.png
Binary files differ
diff --git a/doc/source/modules/gui/widgets/img/TableWidget.png b/doc/source/modules/gui/widgets/img/TableWidget.png
new file mode 100644
index 0000000..a0f819f
--- /dev/null
+++ b/doc/source/modules/gui/widgets/img/TableWidget.png
Binary files differ
diff --git a/doc/source/modules/gui/widgets/img/ThreadPoolPushButton.png b/doc/source/modules/gui/widgets/img/ThreadPoolPushButton.png
new file mode 100644
index 0000000..4710d16
--- /dev/null
+++ b/doc/source/modules/gui/widgets/img/ThreadPoolPushButton.png
Binary files differ
diff --git a/doc/source/modules/gui/widgets/img/WaitingPushButton.png b/doc/source/modules/gui/widgets/img/WaitingPushButton.png
new file mode 100644
index 0000000..fa1d51a
--- /dev/null
+++ b/doc/source/modules/gui/widgets/img/WaitingPushButton.png
Binary files differ
diff --git a/doc/source/modules/gui/widgets/index.rst b/doc/source/modules/gui/widgets/index.rst
index 2284f22..72f89a8 100644
--- a/doc/source/modules/gui/widgets/index.rst
+++ b/doc/source/modules/gui/widgets/index.rst
@@ -14,6 +14,7 @@ Public modules:
framebrowser.rst
periodictable.rst
+ printpreview.rst
tablewidget.rst
threadpoolpushbutton.rst
waitingpushbutton.rst
diff --git a/doc/source/modules/gui/widgets/printpreview.rst b/doc/source/modules/gui/widgets/printpreview.rst
new file mode 100644
index 0000000..d0b7999
--- /dev/null
+++ b/doc/source/modules/gui/widgets/printpreview.rst
@@ -0,0 +1,60 @@
+
+.. currentmodule:: silx.gui.widgets
+
+:mod:`PrintPreview`: Print preview dialog
+-----------------------------------------
+
+.. automodule:: silx.gui.widgets.PrintPreview
+
+Widgets
++++++++
+
+.. autoclass:: silx.gui.widgets.PrintPreview.PrintPreviewDialog
+ :members:
+ :exclude-members: printDialog, showEvent
+ :show-inheritance:
+
+
+.. autoclass:: silx.gui.widgets.PrintPreview.SingletonPrintPreviewDialog
+ :show-inheritance:
+
+Example
++++++++
+
+.. code-block:: python
+
+ import sys
+ from silx.gui import qt
+ from silx.gui.widgets import PrintPreviewDialog
+
+ a = qt.QApplication(sys.argv)
+
+ if len(sys.argv) < 2:
+ print("give an image file as parameter please.")
+ sys.exit(1)
+
+ if len(sys.argv) > 2:
+ print("only one parameter please.")
+ sys.exit(1)
+
+ filename = sys.argv[1]
+ w = PrintPreviewDialog()
+ w.resize(400, 500)
+
+ comment = ""
+ for i in range(20):
+ comment += "Line number %d: En un lugar de La Mancha de cuyo nombre ...\n"
+
+ if filename[-3:] == "svg":
+ item = qt.QSvgRenderer(filename, w.page)
+ w.addSvgItem(item, title=filename,
+ comment=comment, commentPosition="CENTER")
+ else:
+ w.addPixmap(qt.QPixmap.fromImage(qt.QImage(filename)),
+ title=filename,
+ comment=comment,
+ commentPosition="CENTER")
+ w.addImage(qt.QImage(filename), comment=comment, commentPosition="LEFT")
+
+ w.exec_()
+ a.exec_()
diff --git a/doc/source/modules/image/backprojection.rst b/doc/source/modules/image/backprojection.rst
new file mode 100644
index 0000000..c45f670
--- /dev/null
+++ b/doc/source/modules/image/backprojection.rst
@@ -0,0 +1,8 @@
+
+.. currentmodule:: silx.image
+
+:mod:`backprojection`: backprojection algorithm
+-----------------------------------------------
+
+.. automodule:: silx.image.backprojection
+ :members: Backprojection
diff --git a/doc/source/modules/image/index.rst b/doc/source/modules/image/index.rst
index abdeb64..89c5642 100644
--- a/doc/source/modules/image/index.rst
+++ b/doc/source/modules/image/index.rst
@@ -11,3 +11,4 @@
medianfilter.rst
shapes.rst
sift.rst
+ backprojection.rst
diff --git a/doc/source/modules/image/projection.rst b/doc/source/modules/image/projection.rst
new file mode 100644
index 0000000..f5f4815
--- /dev/null
+++ b/doc/source/modules/image/projection.rst
@@ -0,0 +1,15 @@
+
+.. currentmodule:: silx.image
+
+:mod:`projection`: projection algorithm
+---------------------------------------
+
+.. warning::
+
+ This module is known to be buggy in version 0.6:
+ Do NOT use it without having checked it provides
+ proper results
+
+.. automodule:: silx.image.projection
+ :members: Projection
+
diff --git a/doc/source/modules/image/reconstruction.rst b/doc/source/modules/image/reconstruction.rst
new file mode 100644
index 0000000..d288581
--- /dev/null
+++ b/doc/source/modules/image/reconstruction.rst
@@ -0,0 +1,14 @@
+
+.. currentmodule:: silx.image
+
+:mod:`reconstruction`: reconstruction algorithm
+-----------------------------------------------
+
+.. warning::
+
+ This module is known to be buggy in version 0.6:
+ Do NOT use it without having checked it provides
+ proper results
+
+.. automodule:: silx.image.reconstruction
+ :members: ReconstructionAlgorithm, SIRT, TV
diff --git a/doc/source/modules/io/convert.rst b/doc/source/modules/io/convert.rst
new file mode 100644
index 0000000..4d3abdc
--- /dev/null
+++ b/doc/source/modules/io/convert.rst
@@ -0,0 +1,8 @@
+
+.. currentmodule:: silx.io
+
+:mod:`convert`: HDF5 conversion
+-------------------------------
+
+.. automodule:: silx.io.convert
+ :members: write_to_h5, convert
diff --git a/doc/source/modules/io/index.rst b/doc/source/modules/io/index.rst
index 1538a74..815a094 100644
--- a/doc/source/modules/io/index.rst
+++ b/doc/source/modules/io/index.rst
@@ -9,13 +9,13 @@
:maxdepth: 1
configdict.rst
+ convert.rst
dictdump.rst
nxdata.rst
octaveh5.rst
specfile.rst
specfilewrapper.rst
spech5.rst
- spectoh5.rst
utils.rst
Top-level functions
@@ -23,3 +23,8 @@ Top-level functions
.. autofunction:: silx.io.open
.. autofunction:: silx.io.save1D
+
+.. autofunction:: silx.io.is_dataset
+.. autofunction:: silx.io.is_group
+.. autofunction:: silx.io.is_file
+.. autofunction:: silx.io.is_softlink
diff --git a/doc/source/modules/io/spech5.rst b/doc/source/modules/io/spech5.rst
index 143d3bb..61e0083 100644
--- a/doc/source/modules/io/spech5.rst
+++ b/doc/source/modules/io/spech5.rst
@@ -5,7 +5,41 @@
-----------------------------------------
.. automodule:: silx.io.spech5
+
+
+Classes
++++++++
+
+- :class:`SpecH5`
+- :class:`SpecH5Group`
+- :class:`SpecH5Dataset`
+
+.. autoclass:: SpecH5
+ :members:
+ :show-inheritance:
+ :undoc-members:
+ :inherited-members: name, basename, attrs, h5py_class, parent,
+ get, keys, values, items,
+ :special-members: __getitem__, __len__, __contains__, __enter__, __exit__, __iter__
+ :exclude-members: add_node
+
+.. autoclass:: SpecH5Group
+ :show-inheritance:
+
+.. autoclass:: silx.io.commonh5.Group
+ :show-inheritance:
+ :undoc-members:
+ :members: name, basename, file, attrs, h5py_class, parent,
+ get, keys, values, items, visit, visititems
+ :special-members: __getitem__, __len__, __contains__, __iter__
+ :exclude-members: add_node
+
+.. autoclass:: SpecH5Dataset
+ :show-inheritance:
+
+.. autoclass:: SpecH5NodeDataset
:members:
:show-inheritance:
:undoc-members:
- :special-members: __getitem__, __len__, __contains__
+ :inherited-members:
+ :special-members: __getitem__, __len__, __iter__, __getattr__
diff --git a/doc/source/modules/io/spectoh5.rst b/doc/source/modules/io/spectoh5.rst
deleted file mode 100644
index 05fc768..0000000
--- a/doc/source/modules/io/spectoh5.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-
-.. currentmodule:: silx.io
-
-:mod:`spectoh5`: SpecFile to HDF5 conversion
----------------------------------------------
-
-.. automodule:: silx.io.spectoh5
- :members: write_spec_to_h5, convert
diff --git a/doc/source/modules/utils/decorators.rst b/doc/source/modules/utils/decorators.rst
index 1da0f44..c181220 100644
--- a/doc/source/modules/utils/decorators.rst
+++ b/doc/source/modules/utils/decorators.rst
@@ -1,7 +1,7 @@
.. currentmodule:: silx.utils
-:mod:`decorators`
+:mod:`deprecation`
------------------
-.. automodule:: silx.utils.decorators
- :members:
+.. automodule:: silx.utils.deprecation
+ :members: deprecated
diff --git a/doc/source/sample_code/img/animatedicons.png b/doc/source/sample_code/img/animatedicons.png
new file mode 100644
index 0000000..c6f02d8
--- /dev/null
+++ b/doc/source/sample_code/img/animatedicons.png
Binary files differ
diff --git a/doc/source/sample_code/img/customHdf5TreeModel.png b/doc/source/sample_code/img/customHdf5TreeModel.png
new file mode 100644
index 0000000..fff6db5
--- /dev/null
+++ b/doc/source/sample_code/img/customHdf5TreeModel.png
Binary files differ
diff --git a/doc/source/sample_code/img/fftPlotAction.png b/doc/source/sample_code/img/fftPlotAction.png
new file mode 100644
index 0000000..beb3ae3
--- /dev/null
+++ b/doc/source/sample_code/img/fftPlotAction.png
Binary files differ
diff --git a/doc/source/sample_code/img/hdf5widget.png b/doc/source/sample_code/img/hdf5widget.png
new file mode 100644
index 0000000..90ef758
--- /dev/null
+++ b/doc/source/sample_code/img/hdf5widget.png
Binary files differ
diff --git a/doc/source/sample_code/img/icons.png b/doc/source/sample_code/img/icons.png
new file mode 100644
index 0000000..927184e
--- /dev/null
+++ b/doc/source/sample_code/img/icons.png
Binary files differ
diff --git a/doc/source/sample_code/img/imageview.png b/doc/source/sample_code/img/imageview.png
new file mode 100644
index 0000000..b46cd97
--- /dev/null
+++ b/doc/source/sample_code/img/imageview.png
Binary files differ
diff --git a/doc/source/sample_code/img/periodicTable.png b/doc/source/sample_code/img/periodicTable.png
new file mode 100644
index 0000000..85aea6c
--- /dev/null
+++ b/doc/source/sample_code/img/periodicTable.png
Binary files differ
diff --git a/doc/source/sample_code/img/plotContextMenu.png b/doc/source/sample_code/img/plotContextMenu.png
new file mode 100644
index 0000000..2ca7386
--- /dev/null
+++ b/doc/source/sample_code/img/plotContextMenu.png
Binary files differ
diff --git a/doc/source/sample_code/img/plotItemsSelector.png b/doc/source/sample_code/img/plotItemsSelector.png
new file mode 100644
index 0000000..f040472
--- /dev/null
+++ b/doc/source/sample_code/img/plotItemsSelector.png
Binary files differ
diff --git a/doc/source/sample_code/img/plotLimits.png b/doc/source/sample_code/img/plotLimits.png
new file mode 100644
index 0000000..4ca3d29
--- /dev/null
+++ b/doc/source/sample_code/img/plotLimits.png
Binary files differ
diff --git a/doc/source/sample_code/img/plotUpdateFromThread.png b/doc/source/sample_code/img/plotUpdateFromThread.png
new file mode 100644
index 0000000..ac97ccd
--- /dev/null
+++ b/doc/source/sample_code/img/plotUpdateFromThread.png
Binary files differ
diff --git a/doc/source/sample_code/img/plotWidget.png b/doc/source/sample_code/img/plotWidget.png
new file mode 100644
index 0000000..7385ee5
--- /dev/null
+++ b/doc/source/sample_code/img/plotWidget.png
Binary files differ
diff --git a/doc/source/sample_code/img/printPreview.png b/doc/source/sample_code/img/printPreview.png
new file mode 100644
index 0000000..fb75025
--- /dev/null
+++ b/doc/source/sample_code/img/printPreview.png
Binary files differ
diff --git a/doc/source/sample_code/img/scatterMask.png b/doc/source/sample_code/img/scatterMask.png
new file mode 100644
index 0000000..b2028a1
--- /dev/null
+++ b/doc/source/sample_code/img/scatterMask.png
Binary files differ
diff --git a/doc/source/sample_code/img/shiftPlotAction.png b/doc/source/sample_code/img/shiftPlotAction.png
new file mode 100644
index 0000000..afa51eb
--- /dev/null
+++ b/doc/source/sample_code/img/shiftPlotAction.png
Binary files differ
diff --git a/doc/source/sample_code/img/simplewidget.png b/doc/source/sample_code/img/simplewidget.png
new file mode 100644
index 0000000..b4ed9dd
--- /dev/null
+++ b/doc/source/sample_code/img/simplewidget.png
Binary files differ
diff --git a/doc/source/sample_code/img/stackView.png b/doc/source/sample_code/img/stackView.png
new file mode 100644
index 0000000..6003f0a
--- /dev/null
+++ b/doc/source/sample_code/img/stackView.png
Binary files differ
diff --git a/doc/source/sample_code/img/syncaxis.png b/doc/source/sample_code/img/syncaxis.png
new file mode 100644
index 0000000..fd15353
--- /dev/null
+++ b/doc/source/sample_code/img/syncaxis.png
Binary files differ
diff --git a/doc/source/sample_code/img/viewer3DVolume.png b/doc/source/sample_code/img/viewer3DVolume.png
new file mode 100644
index 0000000..69abf26
--- /dev/null
+++ b/doc/source/sample_code/img/viewer3DVolume.png
Binary files differ
diff --git a/doc/source/sample_code/index.rst b/doc/source/sample_code/index.rst
new file mode 100644
index 0000000..6c826b7
--- /dev/null
+++ b/doc/source/sample_code/index.rst
@@ -0,0 +1,232 @@
+.. _sample-code:
+
+Sample Code
+===========
+
+All sample codes can be downloaded as a zip file: |sample_code_archive|.
+
+.. |sample_code_archive| archive:: ../../../examples/
+ :filename: silx_examples.zip
+ :basedir: silx_examples
+ :filter: *.py *.png
+
+:mod:`silx.gui` sample code
++++++++++++++++++++++++++++
+
+.. list-table::
+ :widths: 1 1 4
+ :header-rows: 1
+
+ * - Source
+ - Screenshot
+ - Description
+ * - :download:`animatedicons.py <../../../examples/animatedicons.py>`
+ - .. image:: img/animatedicons.png
+ :height: 150px
+ :align: center
+ - Display available project icons using Qt.
+ * - :download:`customHdf5TreeModel.py <../../../examples/customHdf5TreeModel.py>`
+ - .. image:: img/customHdf5TreeModel.png
+ :height: 150px
+ :align: center
+ - Qt Hdf5 widget examples
+ * - :download:`hdf5widget.py <../../../examples/hdf5widget.py>`
+ - .. image:: img/hdf5widget.png
+ :height: 150px
+ :align: center
+ - 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.
+ * - :download:`icons.py <../../../examples/icons.py>`
+ - .. image:: img/icons.png
+ :height: 150px
+ :align: center
+ - Display available project icons using Qt.
+ * - :download:`periodicTable.py <../../../examples/periodicTable.py>`
+ - .. image:: img/periodicTable.png
+ :height: 150px
+ :align: center
+ - This script is a simple example of how to use the periodic table widgets,
+ select elements and connect signals.
+ * - :download:`simplewidget.py <../../../examples/simplewidget.py>`
+ - .. image:: img/simplewidget.png
+ :height: 150px
+ :align: center
+ - This script shows a gallery of simple widgets provided by silx.
+
+ It shows the following widgets:
+
+ - :class:WaitingPushButton: A button with a progress-like waiting animated icon
+ * - :download:`viewer3DVolume.py <../../../examples/viewer3DVolume.py>`
+ - .. image:: img/viewer3DVolume.png
+ :height: 150px
+ :align: center
+ - This script illustrates the use of 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.gui.plot` sample code
+++++++++++++++++++++++++++++++++
+
+.. list-table::
+ :widths: 1 1 4
+ :header-rows: 1
+
+ * - Source
+ - Screenshot
+ - Description
+ * - :download:`fftPlotAction.py <../../../examples/fftPlotAction.py>`,
+ :download:`fft.png <../../../examples/fft.png>`
+ - .. image:: img/fftPlotAction.png
+ :height: 150px
+ :align: center
+ - This script is a simple example of how to create a PlotWindow with a custom
+ PlotAction added to the toolbar.
+
+ The action computes the FFT of all curves and plots their amplitude spectrum.
+ It also performs the reverse transform.
+
+ This example illustrates:
+ - how to create a checkable action
+ - how to store user info with a curve in a PlotWindow
+ - how to modify the graph title and axes labels
+ - how to add your own icon as a PNG file
+
+ See shiftPlotAction.py for a simpler example with more basic comments.
+ * - :download:`imageview.py <../../../examples/imageview.py>`
+ - .. image:: img/imageview.png
+ :height: 150px
+ :align: center
+ - Example to show the use of `ImageView` widget. It can be used to open an EDF
+ or TIFF file from the shell command line.
+
+ 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>``
+ * - :download:`plotContextMenu.py <../../../examples/plotContextMenu.py>`
+ - .. image:: img/plotContextMenu.png
+ :height: 150px
+ :align: center
+ - This script illustrates the addition of a context menu to a PlotWidget.
+
+ This is done by adding a custom context menu to the plot area of PlotWidget:
+ - set the context menu policy of the plot area to Qt.CustomContextMenu.
+ - connect to the plot area customContextMenuRequested signal.
+
+ The same method works with PlotWindow, Plot1D and Plot2D widgets as they
+ inherit from PlotWidget.
+
+ For more information on context menus, see Qt documentation.
+ * - :download:`plotItemsSelector.py <../../../examples/plotItemsSelector.py>`
+ - .. image:: img/plotItemsSelector.png
+ :height: 150px
+ :align: center
+ - This example illustrates how to use a :class:`ItemsSelectionDialog` widget
+ associated with a :class:`PlotWidget`.
+ * - :download:`plotLimits.py <../../../examples/plotLimits.py>`
+ - .. image:: img/plotLimits.png
+ :height: 150px
+ :align: center
+ - This script is an example to illustrate how to use axis synchronization
+ tool.
+ * - :download:`plotUpdateFromThread.py <../../../examples/plotUpdateFromThread.py>`
+ - .. image:: img/plotUpdateFromThread.png
+ :height: 150px
+ :align: center
+ - This script illustrates the update of a silx.gui.plot widget from a thread.
+
+ The problem is that plot and GUI methods should be called from the main thread.
+ To safely update the plot from another thread, one need to make the update
+ asynchronously from the main thread.
+ In this example, this is achieved through a Qt signal.
+
+ In this example we create a subclass of :class:`silx.gui.plot.Plot1D`
+ that adds a thread-safe method to add curves:
+ :meth:`ThreadSafePlot1D.addCurveThreadSafe`.
+ This thread-safe method is then called from a thread to update the plot.
+ * - :download:`plotWidget.py <../../../examples/plotWidget.py>`
+ - .. image:: img/plotWidget.png
+ :height: 150px
+ :align: center
+ - This script shows how to subclass :class:`PlotWidget` to tune its tools.
+
+ It subclasses a :class:`silx.gui.plot.PlotWidget` and adds toolbars and
+ a colorbar by using pluggable widgets:
+
+ - QAction from :mod:`silx.gui.plot.actions`
+ - QToolButton from :mod:`silx.gui.plot.PlotToolButtons`
+ - QToolBar from :mod:`silx.gui.plot.PlotTools`
+ - :class:`ColorBarWidget` from :mod:`silx.gui.plot.ColorBar`.
+ * - :download:`printPreview.py <../../../examples/printPreview.py>`
+ - .. image:: img/printPreview.png
+ :height: 150px
+ :align: center
+ - This script illustrates how to add a print preview tool button to any plot
+ widget inheriting :class:`PlotWidget`.
+
+ Three plot widgets are instantiated. One of them uses a standalone
+ :class:`PrintPreviewToolButton`, while the other two use a
+ :class:`SingletonPrintPreviewToolButton` which allows them to send their content
+ to the same print preview page.
+ * - :download:`scatterMask.py <../../../examples/scatterMask.py>`
+ - .. image:: img/scatterMask.png
+ :height: 150px
+ :align: center
+ - This example demonstrates how to use ScatterMaskToolsWidget
+ and NamedScatterAlphaSlider with a PlotWidget.
+ * - :download:`shiftPlotAction.py <../../../examples/shiftPlotAction.py>`
+ - .. image:: img/shiftPlotAction.png
+ :height: 150px
+ :align: center
+ - This script is a simple (trivial) example of how to create a PlotWindow,
+ create a custom :class:`PlotAction` and add it to the toolbar.
+
+ The action simply shifts the selected curve up by 1 unit by adding 1 to each
+ value of y.
+ * - :download:`stackView.py <../../../examples/stackView.py>`
+ - .. image:: img/stackView.png
+ :height: 150px
+ :align: center
+ - This script is a simple example to illustrate how to use the StackView
+ widget.
+ * - :download:`syncaxis.py <../../../examples/syncaxis.py>`
+ - .. image:: img/syncaxis.png
+ :height: 150px
+ :align: center
+ - This script is an example to illustrate how to use axis synchronization
+ tool.
+
+:mod:`silx.io` sample code
+++++++++++++++++++++++++++
+
+.. list-table::
+ :widths: 1 1 4
+ :header-rows: 1
+
+ * - Source
+ - Screenshot
+ - Description
+ * - :download:`writetoh5.py <../../../examples/writetoh5.py>`
+ -
+ - This script converts a supported data file (SPEC, EDF...) to a HDF5 file.
+
+ By default, it creates a new output file or fails if the output file given
+ on the command line already exist, but the user can choose to overwrite
+ an existing file, or append data to an existing HDF5 file.
+
+ In case of appending data to HDF5 files, the user can choose between ignoring
+ input data if a corresponding dataset already exists in the output file, or
+ overwriting the existing dataset.
+
+ By default, new scans are written to the root (/) of the HDF5 file, but it is
+ possible to specify a different target path.
diff --git a/doc/source/tutorials.rst b/doc/source/tutorials.rst
index 8ea536b..97698cd 100644
--- a/doc/source/tutorials.rst
+++ b/doc/source/tutorials.rst
@@ -1,5 +1,5 @@
-Tutorials
-=========
+Tutorials and sample code
+=========================
Tutorials and cookbooks:
@@ -7,11 +7,17 @@ Tutorials and cookbooks:
:maxdepth: 1
modules/gui/plot/getting_started.rst
- modules/gui/plot/plotactions_examples.rst
+ modules/gui/plot/actions/examples.rst
modules/gui/designer.rst
Tutorials/Sift/sift.rst
+ Tutorials/io.rst
Tutorials/specfile_to_hdf5.rst
modules/gui/hdf5/getting_started.rst
Tutorials/fit.rst
Tutorials/fitconfig.rst
Tutorials/array_widget.rst
+
+.. toctree::
+ :maxdepth: 2
+
+ sample_code/index.rst
diff --git a/examples/customHdf5TreeModel.py b/examples/customHdf5TreeModel.py
new file mode 100644
index 0000000..8bd444a
--- /dev/null
+++ b/examples/customHdf5TreeModel.py
@@ -0,0 +1,290 @@
+#!/usr/bin/env python
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2016-2017 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.
+#
+# ###########################################################################*/
+"""Qt Hdf5 widget examples
+"""
+
+import logging
+import sys
+import tempfile
+import numpy
+import h5py
+
+logging.basicConfig()
+_logger = logging.getLogger("customHdf5TreeModel")
+"""Module logger"""
+
+from silx.gui import qt
+import silx.gui.hdf5
+from silx.gui.data.DataViewerFrame import DataViewerFrame
+from silx.gui.widgets.ThreadPoolPushButton import ThreadPoolPushButton
+from silx.gui.hdf5.Hdf5TreeModel import Hdf5TreeModel
+
+
+class CustomTooltips(qt.QIdentityProxyModel):
+ """Custom the tooltip of the model by composition.
+
+ It is a very stable way to custom it cause it uses the Qt API. Then it will
+ not change according to the version of Silx.
+
+ But it is not well integrated if you only want to add custom fields to the
+ default tooltips.
+ """
+
+ def data(self, index, role=qt.Qt.DisplayRole):
+ if role == qt.Qt.ToolTipRole:
+
+ # Reach information from the node
+ sourceIndex = self.mapToSource(index)
+ sourceModel = self.sourceModel()
+ originalTooltip = sourceModel.data(sourceIndex, qt.Qt.ToolTipRole)
+ originalH5pyObject = sourceModel.data(sourceIndex, Hdf5TreeModel.H5PY_OBJECT_ROLE)
+
+ # We can filter according to the column
+ if sourceIndex.column() == Hdf5TreeModel.TYPE_COLUMN:
+ return super(CustomTooltips, self).data(index, role)
+
+ # Let's create our own tooltips
+ template = u"""<html>
+ <dl>
+ <dt><b>Original</b></dt><dd>{original}</dd>
+ <dt><b>Parent name</b></dt><dd>{parent_name}</dd>
+ <dt><b>Name</b></dt><dd>{name}</dd>
+ <dt><b>Power of 2</b></dt><dd>{pow_of_2}</dd>
+ </dl>
+ </html>
+ """
+
+ try:
+ data = originalH5pyObject[()]
+ if data.size <= 10:
+ result = data ** 2
+ else:
+ result = "..."
+ except Exception:
+ result = "NA"
+
+ info = dict(
+ original=originalTooltip,
+ parent_name=originalH5pyObject.parent.name,
+ name=originalH5pyObject.name,
+ pow_of_2=str(result)
+ )
+ return template.format(**info)
+
+ return super(CustomTooltips, self).data(index, role)
+
+
+_file_cache = {}
+
+
+def get_hdf5_with_all_types():
+ global _file_cache
+ ID = "alltypes"
+ if ID in _file_cache:
+ return _file_cache[ID].name
+
+ tmp = tempfile.NamedTemporaryFile(prefix=ID + "_", suffix=".h5", delete=True)
+ tmp.file.close()
+ h5 = h5py.File(tmp.name, "w")
+
+ g = h5.create_group("arrays")
+ g.create_dataset("scalar", data=10)
+ g.create_dataset("list", data=numpy.arange(10))
+ base_image = numpy.arange(10**2).reshape(10, 10)
+ images = [base_image,
+ base_image.T,
+ base_image.size - 1 - base_image,
+ base_image.size - 1 - base_image.T]
+ dtype = images[0].dtype
+ data = numpy.empty((10 * 10, 10, 10), dtype=dtype)
+ for i in range(10 * 10):
+ data[i] = images[i % 4]
+ data.shape = 10, 10, 10, 10
+ g.create_dataset("image", data=data[0, 0])
+ g.create_dataset("cube", data=data[0])
+ g.create_dataset("hypercube", data=data)
+ g = h5.create_group("dtypes")
+ g.create_dataset("int32", data=numpy.int32(10))
+ g.create_dataset("int64", data=numpy.int64(10))
+ g.create_dataset("float32", data=numpy.float32(10))
+ g.create_dataset("float64", data=numpy.float64(10))
+ g.create_dataset("string_", data=numpy.string_("Hi!"))
+ # g.create_dataset("string0",data=numpy.string0("Hi!\x00"))
+
+ g.create_dataset("bool", data=True)
+ g.create_dataset("bool2", data=False)
+ h5.close()
+
+ _file_cache[ID] = tmp
+ return tmp.name
+
+
+class Hdf5TreeViewExample(qt.QMainWindow):
+ """
+ This window show an example of use of a Hdf5TreeView.
+
+ The tree is initialized with a list of filenames. A panel allow to play
+ with internal property configuration of the widget, and a text screen
+ allow to display events.
+ """
+
+ def __init__(self, filenames=None):
+ """
+ :param files_: List of HDF5 or Spec files (pathes or
+ :class:`silx.io.spech5.SpecH5` or :class:`h5py.File`
+ instances)
+ """
+ qt.QMainWindow.__init__(self)
+ self.setWindowTitle("Silx HDF5 widget example")
+
+ self.__asyncload = False
+ self.__treeview = silx.gui.hdf5.Hdf5TreeView(self)
+ """Silx HDF5 TreeView"""
+
+ self.__sourceModel = self.__treeview.model()
+ """Store the source model"""
+
+ self.__text = qt.QTextEdit(self)
+ """Widget displaying information"""
+
+ self.__dataViewer = DataViewerFrame(self)
+ vSpliter = qt.QSplitter(qt.Qt.Vertical)
+ vSpliter.addWidget(self.__dataViewer)
+ vSpliter.addWidget(self.__text)
+ vSpliter.setSizes([10, 0])
+
+ spliter = qt.QSplitter(self)
+ spliter.addWidget(self.__treeview)
+ spliter.addWidget(vSpliter)
+ spliter.setStretchFactor(1, 1)
+
+ main_panel = qt.QWidget(self)
+ layout = qt.QVBoxLayout()
+ layout.addWidget(spliter)
+ layout.addWidget(self.createTreeViewConfigurationPanel(self, self.__treeview))
+ layout.setStretchFactor(spliter, 1)
+ main_panel.setLayout(layout)
+
+ self.setCentralWidget(main_panel)
+
+ # append all files to the tree
+ for file_name in filenames:
+ self.__treeview.findHdf5TreeModel().appendFile(file_name)
+
+ self.__treeview.activated.connect(self.displayData)
+
+ def displayData(self):
+ """Called to update the dataviewer with the selected data.
+ """
+ selected = list(self.__treeview.selectedH5Nodes())
+ if len(selected) == 1:
+ # Update the viewer for a single selection
+ data = selected[0]
+ # data is a hdf5.H5Node object
+ # data.h5py_object is a Group/Dataset object (from h5py, spech5, fabioh5)
+ # The dataviewer can display both
+ self.__dataViewer.setData(data)
+
+ def __fileCreated(self, filename):
+ if self.__asyncload:
+ self.__treeview.findHdf5TreeModel().insertFileAsync(filename)
+ else:
+ self.__treeview.findHdf5TreeModel().insertFile(filename)
+
+ def __hdf5ComboChanged(self, index):
+ function = self.__hdf5Combo.itemData(index)
+ self.__createHdf5Button.setCallable(function)
+
+ def __edfComboChanged(self, index):
+ function = self.__edfCombo.itemData(index)
+ self.__createEdfButton.setCallable(function)
+
+ def __useCustomLabel(self):
+ customModel = CustomTooltips(self.__treeview)
+ customModel.setSourceModel(self.__sourceModel)
+ self.__treeview.setModel(customModel)
+
+ def __useOriginalModel(self):
+ self.__treeview.setModel(self.__sourceModel)
+
+ def createTreeViewConfigurationPanel(self, parent, treeview):
+ """Create a configuration panel to allow to play with widget states"""
+ panel = qt.QWidget(parent)
+ panel.setLayout(qt.QHBoxLayout())
+
+ content = qt.QGroupBox("Create HDF5", panel)
+ content.setLayout(qt.QVBoxLayout())
+ panel.layout().addWidget(content)
+
+ combo = qt.QComboBox()
+ combo.addItem("Containing all types", get_hdf5_with_all_types)
+ combo.activated.connect(self.__hdf5ComboChanged)
+ content.layout().addWidget(combo)
+
+ button = ThreadPoolPushButton(content, text="Create")
+ button.setCallable(combo.itemData(combo.currentIndex()))
+ button.succeeded.connect(self.__fileCreated)
+ content.layout().addWidget(button)
+
+ self.__hdf5Combo = combo
+ self.__createHdf5Button = button
+
+ content.layout().addStretch(1)
+
+ option = qt.QGroupBox("Custom model", panel)
+ option.setLayout(qt.QVBoxLayout())
+ panel.layout().addWidget(option)
+
+ button = qt.QPushButton("Original model")
+ button.clicked.connect(self.__useOriginalModel)
+ option.layout().addWidget(button)
+
+ button = qt.QPushButton("Custom tooltips by composition")
+ button.clicked.connect(self.__useCustomLabel)
+ option.layout().addWidget(button)
+
+ option.layout().addStretch(1)
+
+ panel.layout().addStretch(1)
+ return panel
+
+
+def main(filenames):
+ """
+ :param filenames: list of file paths
+ """
+ app = qt.QApplication([])
+ sys.excepthook = qt.exceptionHandler
+ window = Hdf5TreeViewExample(filenames)
+ window.show()
+ result = app.exec_()
+ # remove ending warnings relative to QTimer
+ app.deleteLater()
+ sys.exit(result)
+
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/examples/fftPlotAction.py b/examples/fftPlotAction.py
index e4d4081..66ecfbd 100755
--- a/examples/fftPlotAction.py
+++ b/examples/fftPlotAction.py
@@ -40,7 +40,7 @@ See shiftPlotAction.py for a simpler example with more basic comments.
"""
__authors__ = ["P. Knobel"]
__license__ = "MIT"
-__date__ = "12/01/2017"
+__date__ = "27/06/2017"
import numpy
import os
@@ -48,7 +48,7 @@ import sys
from silx.gui import qt
from silx.gui.plot import PlotWindow
-from silx.gui.plot.PlotActions import PlotAction
+from silx.gui.plot.actions import PlotAction
# Custom icon
# make sure there is a "fft.png" file saved in the same folder as this script
@@ -77,8 +77,8 @@ class FftAction(PlotAction):
def _rememberGraphLabels(self):
"""Store labels and title as attributes"""
self.original_title = self.plot.getGraphTitle()
- self.original_xlabel = self.plot.getGraphXLabel()
- self.original_ylabel = self.plot.getGraphYLabel()
+ self.original_xlabel = self.plot.getXAxis().getLabel()
+ self.original_ylabel = self.plot.getYAxis().getLabel()
def fftAllCurves(self, checked=False):
"""Get all curves from our PlotWindow, compute the amplitude spectrum
@@ -97,13 +97,13 @@ class FftAction(PlotAction):
self._rememberGraphLabels()
# change them
self.plot.setGraphTitle("Amplitude spectrum")
- self.plot.setGraphXLabel("Frequency")
- self.plot.setGraphYLabel("Amplitude")
+ self.plot.getXAxis().setLabel("Frequency")
+ self.plot.getYAxis().setLabel("Amplitude")
else:
# restore original labels
self.plot.setGraphTitle(self.original_title)
- self.plot.setGraphXLabel(self.original_xlabel)
- self.plot.setGraphYLabel(self.original_ylabel)
+ self.plot.getXAxis().setLabel(self.original_xlabel)
+ self.plot.getYAxis().setLabel(self.original_ylabel)
self.plot.clearCurves()
@@ -186,8 +186,8 @@ plotwin.addCurve(x, y2, legend="cos")
plotwin.addCurve(x, y3, legend="square wave")
plotwin.setGraphTitle("Original data")
-plotwin.setGraphYLabel("amplitude")
-plotwin.setGraphXLabel("time")
+plotwin.getYAxis().setLabel("amplitude")
+plotwin.getXAxis().setLabel("time")
plotwin.show()
app.exec_()
diff --git a/examples/icons.py b/examples/icons.py
index 0992cf4..a6f0ada 100644
--- a/examples/icons.py
+++ b/examples/icons.py
@@ -26,10 +26,10 @@
"""
Display available project icons using Qt.
"""
+import functools
+
from silx.gui import qt
import silx.gui.icons
-import pkg_resources
-import functools
class IconPreview(qt.QMainWindow):
@@ -74,9 +74,11 @@ class IconPreview(qt.QMainWindow):
self.tools = []
- icons = pkg_resources.resource_listdir("silx.resources", "gui/icons")
+ import silx.resources
+
+ icons = silx.resources.list_dir("gui/icons")
# filter out sub-directories
- icons = filter(lambda x: not pkg_resources.resource_isdir("silx.resources", "gui/icons/" + x), icons)
+ icons = filter(lambda x: not silx.resources.is_dir("gui/icons/" + x), icons)
# remove extension
icons = [i.split(".")[0] for i in icons]
# remove duplicated names
diff --git a/examples/plotContextMenu.py b/examples/plotContextMenu.py
new file mode 100644
index 0000000..3e9af1e
--- /dev/null
+++ b/examples/plotContextMenu.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2017 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 script illustrates the addition of a context menu to a PlotWidget.
+
+This is done by adding a custom context menu to the plot area of PlotWidget:
+- set the context menu policy of the plot area to Qt.CustomContextMenu.
+- connect to the plot area customContextMenuRequested signal.
+
+The same method works with PlotWindow, Plot1D and Plot2D widgets as they
+inherit from PlotWidget.
+
+For more information on context menus, see Qt documentation.
+"""
+
+import numpy
+
+from silx.gui import qt
+from silx.gui.plot import PlotWidget
+from silx.gui.plot.actions.control import ZoomBackAction, CrosshairAction
+from silx.gui.plot.actions.io import SaveAction, PrintAction
+
+
+class PlotWidgetWithContextMenu(PlotWidget):
+ """This class adds a custom context menu to PlotWidget's plot area."""
+
+ def __init__(self, *args, **kwargs):
+ super(PlotWidgetWithContextMenu, self).__init__(*args, **kwargs)
+ self.setWindowTitle('PlotWidget with a context menu')
+ self.setGraphTitle('Right-click on the plot to access context menu')
+
+ # Create QAction for the context menu once for all
+ self._zoomBackAction = ZoomBackAction(plot=self, parent=self)
+ self._crosshairAction = CrosshairAction(plot=self, parent=self)
+ self._saveAction = SaveAction(plot=self, parent=self)
+ self._printAction = PrintAction(plot=self, parent=self)
+
+ # Retrieve PlotWidget's plot area widget
+ plotArea = self.getWidgetHandle()
+
+ # Set plot area custom context menu
+ plotArea.setContextMenuPolicy(qt.Qt.CustomContextMenu)
+ plotArea.customContextMenuRequested.connect(self._contextMenu)
+
+ def _contextMenu(self, pos):
+ """Handle plot area customContextMenuRequested signal.
+
+ :param QPoint pos: Mouse position relative to plot area
+ """
+ # Create the context menu
+ menu = qt.QMenu(self)
+ menu.addAction(self._zoomBackAction)
+ menu.addSeparator()
+ menu.addAction(self._crosshairAction)
+ menu.addSeparator()
+ menu.addAction(self._saveAction)
+ menu.addAction(self._printAction)
+
+ # Displaying the context menu at the mouse position requires
+ # a global position.
+ # The position received as argument is relative to PlotWidget's
+ # plot area, and thus needs to be converted.
+ plotArea = self.getWidgetHandle()
+ globalPosition = plotArea.mapToGlobal(pos)
+ menu.exec_(globalPosition)
+
+
+# Start the QApplication
+app = qt.QApplication([]) # Start QApplication
+plot = PlotWidgetWithContextMenu() # Create the widget
+
+# Add content to the plot
+x = numpy.linspace(0, 2 * numpy.pi, 1000)
+plot.addCurve(x, numpy.sin(x), legend='sin')
+
+# Show the widget and start the application
+plot.show()
+app.exec_()
diff --git a/examples/colorbar.py b/examples/plotItemsSelector.py
index a4dc2d8..8f29457 100644..100755
--- a/examples/colorbar.py
+++ b/examples/plotItemsSelector.py
@@ -2,7 +2,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2016-2017 European Synchrotron Radiation Facility
+# Copyright (c) 2017 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,40 +23,34 @@
# THE SOFTWARE.
#
# ###########################################################################*/
+"""This example illustrates how to use a :class:`ItemsSelectionDialog` widget
+associated with a :class:`PlotWidget`.
"""
-Example to show the use of `ColorBarWidget` widget.
-It can be associated to a plot.
-In this exqmple the `ColorBarWidget` widget will display the colormap of the
-active image.
-
-To change the active image slick on the image you want to set active.
-"""
-
-__authors__ = ["H. Payno"]
+__authors__ = ["P. Knobel"]
__license__ = "MIT"
-__date__ = "03/05/2017"
-
+__date__ = "28/06/2017"
from silx.gui import qt
-import numpy
-from silx.gui.plot import Plot2D
-from silx.gui.plot.ColorBar import ColorBarWidget
-
-image = numpy.exp(numpy.random.rand(100, 100) * 10)
+from silx.gui.plot.PlotWidget import PlotWidget
+from silx.gui.plot.ItemsSelectionDialog import ItemsSelectionDialog
app = qt.QApplication([])
-plot = Plot2D()
-colorbar = ColorBarWidget(parent=None, plot=plot)
-colorbar.setLegend('my colormap')
-colorbar.show()
-plot.show()
-
-clm = plot.getDefaultColormap()
-clm['normalization'] = 'log'
-clm['name'] = 'viridis'
-plot.addImage(data=image, colormap=clm, legend='image')
-plot.setActiveImage('image')
+pw = PlotWidget()
+pw.addCurve([0, 1, 2], [3, 2, 1], "A curve")
+pw.addScatter([0, 1, 2.5], [3, 2.5, 0.9], [8, 9, 72], "A scatter")
+pw.addHistogram([0, 1, 2.5], [0, 1, 2, 3], "A histogram")
+pw.addImage([[0, 1, 2], [3, 2, 1]], "An image")
+pw.show()
+
+isd = ItemsSelectionDialog(plot=pw)
+isd.setItemsSelectionMode(qt.QTableWidget.ExtendedSelection)
+result = isd.exec_()
+if result:
+ for item in isd.getSelectedItems():
+ print(item.getLegend(), type(item))
+else:
+ print("Selection cancelled")
app.exec_()
diff --git a/examples/plotLimits.py b/examples/plotLimits.py
new file mode 100644
index 0000000..0a39bc6
--- /dev/null
+++ b/examples/plotLimits.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2016-2017 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 script is an example to illustrate how to use axis synchronization
+tool.
+"""
+
+from silx.gui import qt
+from silx.gui import plot
+import numpy
+import silx.test.utils
+
+
+class ConstrainedViewPlot(qt.QMainWindow):
+
+ def __init__(self):
+ qt.QMainWindow.__init__(self)
+ self.setWindowTitle("Plot with synchronized axes")
+ widget = qt.QWidget(self)
+ self.setCentralWidget(widget)
+
+ layout = qt.QGridLayout()
+ widget.setLayout(layout)
+
+ backend = "mpl"
+
+ data = numpy.arange(100 * 100)
+ data = (data % 100) / 5.0
+ data = numpy.sin(data)
+ data = silx.test.utils.add_gaussian_noise(data, mean=0.01)
+ data.shape = 100, 100
+
+ data1d = numpy.mean(data, axis=0)
+
+ self.plot2d = plot.Plot2D(parent=widget, backend=backend)
+ self.plot2d.setGraphTitle("A pixel can't be too big")
+ self.plot2d.setInteractiveMode('pan')
+ self.plot2d.addImage(data)
+ self.plot2d.getXAxis().setRangeConstraints(minRange=10)
+ self.plot2d.getYAxis().setRangeConstraints(minRange=10)
+
+ self.plot2d2 = plot.Plot2D(parent=widget, backend=backend)
+ self.plot2d2.setGraphTitle("The image can't be too small")
+ self.plot2d2.setInteractiveMode('pan')
+ self.plot2d2.addImage(data)
+ self.plot2d2.getXAxis().setRangeConstraints(maxRange=200)
+ self.plot2d2.getYAxis().setRangeConstraints(maxRange=200)
+
+ self.plot1d = plot.Plot1D(parent=widget, backend=backend)
+ self.plot1d.setGraphTitle("The curve is clamped into the view")
+ self.plot1d.addCurve(x=numpy.arange(100), y=data1d, legend="mean")
+ self.plot1d.getXAxis().setLimitsConstraints(minPos=0, maxPos=100)
+ self.plot1d.getYAxis().setLimitsConstraints(minPos=data1d.min(), maxPos=data1d.max())
+
+ self.plot1d2 = plot.Plot1D(parent=widget, backend=backend)
+ self.plot1d2.setGraphTitle("Only clamp y-axis")
+ self.plot1d2.setInteractiveMode('pan')
+ self.plot1d2.addCurve(x=numpy.arange(100), y=data1d, legend="mean")
+ self.plot1d2.getYAxis().setLimitsConstraints(minPos=data1d.min(), maxPos=data1d.max())
+
+ layout.addWidget(self.plot2d, 0, 0)
+ layout.addWidget(self.plot1d, 0, 1)
+ layout.addWidget(self.plot2d2, 1, 0)
+ layout.addWidget(self.plot1d2, 1, 1)
+
+
+if __name__ == "__main__":
+ app = qt.QApplication([])
+ window = ConstrainedViewPlot()
+ window.setVisible(True)
+ app.exec_()
diff --git a/examples/plotUpdateFromThread.py b/examples/plotUpdateFromThread.py
new file mode 100644
index 0000000..d36bc48
--- /dev/null
+++ b/examples/plotUpdateFromThread.py
@@ -0,0 +1,128 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2017 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 script illustrates the update of a silx.gui.plot widget from a thread.
+
+The problem is that plot and GUI methods should be called from the main thread.
+To safely update the plot from another thread, one need to make the update
+asynchronously from the main thread.
+In this example, this is achieved through a Qt signal.
+
+In this example we create a subclass of :class:`silx.gui.plot.Plot1D`
+that adds a thread-safe method to add curves:
+:meth:`ThreadSafePlot1D.addCurveThreadSafe`.
+This thread-safe method is then called from a thread to update the plot.
+"""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "05/09/2017"
+
+
+import threading
+import time
+
+import numpy
+
+from silx.gui import qt
+from silx.gui.plot import Plot1D
+
+
+class ThreadSafePlot1D(Plot1D):
+ """Add a thread-safe :meth:`addCurveThreadSafe` method to Plot1D.
+ """
+
+ _sigAddCurve = qt.Signal(tuple, dict)
+ """Signal used to perform addCurve in the main thread.
+
+ It takes args and kwargs as arguments.
+ """
+
+ def __init__(self, parent=None):
+ super(ThreadSafePlot1D, self).__init__(parent)
+ # Connect the signal to the method actually calling addCurve
+ self._sigAddCurve.connect(self.__addCurve)
+
+ def __addCurve(self, args, kwargs):
+ """Private method calling addCurve from _sigAddCurve"""
+ self.addCurve(*args, **kwargs)
+
+ def addCurveThreadSafe(self, *args, **kwargs):
+ """Thread-safe version of :meth:`silx.gui.plot.Plot.addCurve`
+
+ This method takes the same arguments as Plot.addCurve.
+
+ WARNING: This method does not return a value as opposed to Plot.addCurve
+ """
+ self._sigAddCurve.emit(args, kwargs)
+
+
+class UpdateThread(threading.Thread):
+ """Thread updating the curve of a :class:`ThreadSafePlot1D`
+
+ :param plot1d: The ThreadSafePlot1D to update."""
+
+ def __init__(self, plot1d):
+ self.plot1d = plot1d
+ self.running = False
+ super(UpdateThread, self).__init__()
+
+ def start(self):
+ """Start the update thread"""
+ self.running = True
+ super(UpdateThread, self).start()
+
+ def run(self):
+ """Method implementing thread loop that updates the plot"""
+ while self.running:
+ time.sleep(1)
+ self.plot1d.addCurveThreadSafe(
+ numpy.arange(1000), numpy.random.random(1000), resetzoom=False)
+
+ def stop(self):
+ """Stop the update thread"""
+ self.running = False
+ self.join(2)
+
+
+def main():
+ global app
+ app = qt.QApplication([])
+
+ # Create a ThreadSafePlot1D, set its limits and display it
+ plot1d = ThreadSafePlot1D()
+ plot1d.setLimits(0., 1000., 0., 1.)
+ plot1d.show()
+
+ # Create the thread that calls ThreadSafePlot1D.addCurveThreadSafe
+ updateThread = UpdateThread(plot1d)
+ updateThread.start() # Start updating the plot
+
+ app.exec_()
+
+ updateThread.stop() # Stop updating the plot
+
+
+if __name__ == '__main__':
+ main()
diff --git a/examples/plotWidget.py b/examples/plotWidget.py
new file mode 100644
index 0000000..698fe56
--- /dev/null
+++ b/examples/plotWidget.py
@@ -0,0 +1,121 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2017 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 script shows how to subclass :class:`PlotWidget` to tune its tools.
+
+It subclasses a :class:`silx.gui.plot.PlotWidget` and adds toolbars and
+a colorbar by using pluggable widgets:
+
+- QAction from :mod:`silx.gui.plot.actions`
+- QToolButton from :mod:`silx.gui.plot.PlotToolButtons`
+- QToolBar from :mod:`silx.gui.plot.PlotTools`
+- :class:`ColorBarWidget` from :mod:`silx.gui.plot.ColorBar`.
+"""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "05/09/2017"
+
+import numpy
+
+from silx.gui import qt
+import silx.gui.plot
+
+from silx.gui.plot import actions # QAction to use with PlotWidget
+from silx.gui.plot import PlotToolButtons # QToolButton to use with PlotWidget
+from silx.gui.plot.PlotTools import LimitsToolBar
+from silx.gui.plot.ColorBar import ColorBarWidget
+
+class MyPlotWidget(silx.gui.plot.PlotWidget):
+ """PlotWidget with an ad hoc toolbar and a colorbar"""
+
+ def __init__(self, parent=None):
+ super(MyPlotWidget, self).__init__(parent)
+
+ # Add a tool bar to PlotWidget
+ toolBar = qt.QToolBar("Plot Tools", self)
+ self.addToolBar(toolBar)
+
+ # Add actions from silx.gui.plot.action to the toolbar
+ resetZoomAction = actions.control.ResetZoomAction(self, self)
+ toolBar.addAction(resetZoomAction)
+
+ # Add tool buttons from silx.gui.plot.PlotToolButtons
+ aspectRatioButton = PlotToolButtons.AspectToolButton(
+ parent=self, plot=self)
+ toolBar.addWidget(aspectRatioButton)
+
+ # Add limits tool bar from silx.gui.plot.PlotTools
+ limitsToolBar = LimitsToolBar(parent=self, plot=self)
+ self.addToolBar(qt.Qt.BottomToolBarArea, limitsToolBar)
+
+ self._initColorBar()
+
+ def _initColorBar(self):
+ """Create the ColorBarWidget and add it to the PlotWidget"""
+
+ # Add a colorbar on the right side
+ colorBar = ColorBarWidget(parent=self, plot=self)
+
+ # Make ColorBarWidget background white by changing its palette
+ colorBar.setAutoFillBackground(True)
+ palette = colorBar.palette()
+ palette.setColor(qt.QPalette.Background, qt.Qt.white)
+ palette.setColor(qt.QPalette.Window, qt.Qt.white)
+ colorBar.setPalette(palette)
+
+ # Add the ColorBarWidget by changing PlotWidget's central widget
+ gridLayout = qt.QGridLayout()
+ gridLayout.setSpacing(0)
+ gridLayout.setContentsMargins(0, 0, 0, 0)
+ plot = self.getWidgetHandle() # Get the widget rendering the plot
+ gridLayout.addWidget(plot, 0, 0)
+ gridLayout.addWidget(colorBar, 0, 1)
+ gridLayout.setRowStretch(0, 1)
+ gridLayout.setColumnStretch(0, 1)
+ centralWidget = qt.QWidget()
+ centralWidget.setLayout(gridLayout)
+ self.setCentralWidget(centralWidget)
+
+
+def main():
+ global app
+ app = qt.QApplication([])
+
+ # Create the ad hoc plot widget and change its default colormap
+ plot = MyPlotWidget()
+ plot.getDefaultColormap().setName('viridis')
+ plot.show()
+
+ # Add an image to the plot
+ x = numpy.outer(
+ numpy.linspace(-10, 10, 200), numpy.linspace(-10, 5, 150))
+ image = numpy.sin(x) / x
+ plot.addImage(image)
+
+ app.exec_()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/examples/printPreview.py b/examples/printPreview.py
new file mode 100755
index 0000000..187ad84
--- /dev/null
+++ b/examples/printPreview.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2016-2017 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 script illustrates how to add a print preview tool button to any plot
+widget inheriting :class:`PlotWidget`.
+
+Three plot widgets are instantiated. One of them uses a standalone
+:class:`PrintPreviewToolButton`, while the other two use a
+:class:`SingletonPrintPreviewToolButton` which allows them to send their content
+to the same print preview page.
+"""
+__authors__ = ["P. Knobel"]
+__license__ = "MIT"
+__date__ = "25/07/2017"
+
+import numpy
+
+from silx.gui import qt
+from silx.gui.plot import PlotWidget
+from silx.gui.plot import PrintPreviewToolButton
+
+app = qt.QApplication([])
+
+x = numpy.arange(1000)
+
+# first widget has a standalone print preview action
+pw1 = PlotWidget()
+pw1.setWindowTitle("Widget 1 with standalone print preview")
+toolbar1 = qt.QToolBar(pw1)
+toolbutton1 = PrintPreviewToolButton.PrintPreviewToolButton(parent=toolbar1,
+ plot=pw1)
+pw1.addToolBar(toolbar1)
+toolbar1.addWidget(toolbutton1)
+pw1.show()
+pw1.addCurve(x, numpy.tan(x * 2 * numpy.pi / 1000))
+
+# next two plots share a common print preview
+pw2 = PlotWidget()
+pw2.setWindowTitle("Widget 2 with shared print preview")
+toolbar2 = qt.QToolBar(pw2)
+toolbutton2 = PrintPreviewToolButton.SingletonPrintPreviewToolButton(
+ parent=toolbar2, plot=pw2)
+pw2.addToolBar(toolbar2)
+toolbar2.addWidget(toolbutton2)
+pw2.show()
+pw2.addCurve(x, numpy.sin(x * 2 * numpy.pi / 1000))
+
+
+pw3 = PlotWidget()
+pw3.setWindowTitle("Widget 3 with shared print preview")
+toolbar3 = qt.QToolBar(pw3)
+toolbutton3 = PrintPreviewToolButton.SingletonPrintPreviewToolButton(
+ parent=toolbar3, plot=pw3)
+pw3.addToolBar(toolbar3)
+toolbar3.addWidget(toolbutton3)
+pw3.show()
+pw3.addCurve(x, numpy.cos(x * 2 * numpy.pi / 1000))
+
+
+app.exec_()
diff --git a/examples/shiftPlotAction.py b/examples/shiftPlotAction.py
index ca48300..7cac08c 100755
--- a/examples/shiftPlotAction.py
+++ b/examples/shiftPlotAction.py
@@ -37,7 +37,7 @@ __date__ = "12/01/2017"
import sys
from silx.gui import qt
from silx.gui.plot import PlotWindow
-from silx.gui.plot.PlotActions import PlotAction
+from silx.gui.plot.actions import PlotAction
class ShiftUpAction(PlotAction):
diff --git a/examples/syncaxis.py b/examples/syncaxis.py
new file mode 100644
index 0000000..1033738
--- /dev/null
+++ b/examples/syncaxis.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2016-2017 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 script is an example to illustrate how to use axis synchronization
+tool.
+"""
+
+from silx.gui import qt
+from silx.gui import plot
+import numpy
+import silx.test.utils
+from silx.gui.plot.utils.axis import SyncAxes
+
+
+class SyncPlot(qt.QMainWindow):
+
+ def __init__(self):
+ qt.QMainWindow.__init__(self)
+ self.setWindowTitle("Plot with synchronized axes")
+ widget = qt.QWidget(self)
+ self.setCentralWidget(widget)
+
+ layout = qt.QGridLayout()
+ widget.setLayout(layout)
+
+ backend = "mpl"
+ self.plot2d = plot.Plot2D(parent=widget, backend=backend)
+ self.plot2d.setInteractiveMode('pan')
+ self.plot1d_x1 = plot.Plot1D(parent=widget, backend=backend)
+ self.plot1d_x2 = plot.PlotWidget(parent=widget, backend=backend)
+ self.plot1d_y1 = plot.Plot1D(parent=widget, backend=backend)
+ self.plot1d_y2 = plot.PlotWidget(parent=widget, backend=backend)
+
+ data = numpy.arange(100 * 100)
+ data = (data % 100) / 5.0
+ data = numpy.sin(data)
+ data = silx.test.utils.add_gaussian_noise(data, mean=0.01)
+ data.shape = 100, 100
+
+ self.plot2d.addImage(data)
+ self.plot1d_x1.addCurve(x=numpy.arange(100), y=numpy.mean(data, axis=0), legend="mean")
+ self.plot1d_x1.addCurve(x=numpy.arange(100), y=numpy.max(data, axis=0), legend="max")
+ self.plot1d_x1.addCurve(x=numpy.arange(100), y=numpy.min(data, axis=0), legend="min")
+ self.plot1d_x2.addCurve(x=numpy.arange(100), y=numpy.std(data, axis=0))
+
+ self.plot1d_y1.addCurve(y=numpy.arange(100), x=numpy.mean(data, axis=1), legend="mean")
+ self.plot1d_y1.addCurve(y=numpy.arange(100), x=numpy.max(data, axis=1), legend="max")
+ self.plot1d_y1.addCurve(y=numpy.arange(100), x=numpy.min(data, axis=1), legend="min")
+ self.plot1d_y2.addCurve(y=numpy.arange(100), x=numpy.std(data, axis=1))
+
+ self.constraint1 = SyncAxes([self.plot2d.getXAxis(), self.plot1d_x1.getXAxis(), self.plot1d_x2.getXAxis()])
+ self.constraint2 = SyncAxes([self.plot2d.getYAxis(), self.plot1d_y1.getYAxis(), self.plot1d_y2.getYAxis()])
+ self.constraint3 = SyncAxes([self.plot1d_x1.getYAxis(), self.plot1d_y1.getXAxis()])
+ self.constraint4 = SyncAxes([self.plot1d_x2.getYAxis(), self.plot1d_y2.getXAxis()])
+
+ layout.addWidget(self.plot2d, 0, 0)
+ layout.addWidget(self.createCenteredLabel(u"↓↑"), 1, 0)
+ layout.addWidget(self.plot1d_x1, 2, 0)
+ layout.addWidget(self.createCenteredLabel(u"↓↑"), 3, 0)
+ layout.addWidget(self.plot1d_x2, 4, 0)
+ layout.addWidget(self.createCenteredLabel(u"→\n←"), 0, 1)
+ layout.addWidget(self.plot1d_y1, 0, 2)
+ layout.addWidget(self.createCenteredLabel(u"→\n←"), 0, 3)
+ layout.addWidget(self.plot1d_y2, 0, 4)
+ layout.addWidget(self.createCenteredLabel(u"↗↙"), 2, 2)
+ layout.addWidget(self.createCenteredLabel(u"↗↙"), 4, 4)
+
+ def createCenteredLabel(self, text):
+ label = qt.QLabel(self)
+ label.setAlignment(qt.Qt.AlignCenter)
+ label.setText(text)
+ return label
+
+
+if __name__ == "__main__":
+ app = qt.QApplication([])
+ window = SyncPlot()
+ window.setVisible(True)
+ app.exec_()
diff --git a/examples/viewer3DVolume.py b/examples/viewer3DVolume.py
index 7a95d8a..82022f9 100644
--- a/examples/viewer3DVolume.py
+++ b/examples/viewer3DVolume.py
@@ -101,6 +101,19 @@ def load(filename):
return data
+def default_isolevel(data):
+ """Compute a default isosurface level: mean + 1 std
+
+ :param numpy.ndarray data: The data to process
+ :rtype: float
+ """
+ data = data[numpy.isfinite(data)]
+ if len(data) == 0:
+ return 0
+ else:
+ return numpy.mean(data) + numpy.std(data)
+
+
def main(argv=None):
# Parse input arguments
parser = argparse.ArgumentParser(
@@ -171,11 +184,11 @@ def main(argv=None):
else:
# Create dummy data
_logger.warning('Not data file provided, creating dummy data')
- size = 128
- z, y, x = numpy.mgrid[0:size, 0:size, 0:size]
- data = numpy.asarray(
- size**2 - ((x-size/2)**2 + (y-size/2)**2 + (z-size/2)**2),
- dtype='float32')
+ coords = numpy.linspace(-10, 10, 64)
+ z = coords.reshape(-1, 1, 1)
+ y = coords.reshape(1, -1, 1)
+ x = coords.reshape(1, 1, -1)
+ data = numpy.sin(x * y * z) / (x * y * z)
# Set ScalarFieldView data
window.setData(data)
@@ -195,9 +208,7 @@ def main(argv=None):
window.addIsosurface(args.level, '#FF0000FF')
else:
# Add an iso-surface from a function
- window.addIsosurface(
- lambda data: numpy.mean(data) + numpy.std(data),
- '#FF0000FF')
+ window.addIsosurface(default_isolevel, '#FF0000FF')
window.show()
return app.exec_()
diff --git a/examples/spectoh5.py b/examples/writetoh5.py
index f2f796b..5e89e48 100755..100644
--- a/examples/spectoh5.py
+++ b/examples/writetoh5.py
@@ -2,7 +2,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2004-2016 European Synchrotron Radiation Facility
+# Copyright (c) 2004-2017 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,7 +23,7 @@
# THE SOFTWARE.
#
# ###########################################################################*/
-"""This script converts SPEC data files to HDF5 files.
+"""This script converts a supported data file (SPEC, EDF,...) to a HDF5 file.
By default, it creates a new output file or fails if the output file given
on the command line already exist, but the user can choose to overwrite
@@ -39,14 +39,14 @@ possible to specify a different target path.
__authors__ = ["P. Knobel"]
__license__ = "MIT"
-__date__ = "18/10/2016"
+__date__ = "12/09/2016"
import argparse
-from silx.io.spectoh5 import write_spec_to_h5
+from silx.io.convert import write_to_h5
parser = argparse.ArgumentParser(description=__doc__)
-parser.add_argument('spec_path',
- help='Path to input SPEC data file')
+parser.add_argument('input_path',
+ help='Path to input data file')
parser.add_argument('h5_path',
help='Path to output HDF5 file')
parser.add_argument('-t', '--target-path', default="/",
@@ -82,7 +82,7 @@ else:
# by default, use "write" mode and fail if file already exists
mode = "w-"
-write_spec_to_h5(args.spec_path, args.h5_path,
- h5path=args.target_path,
- mode=mode,
- overwrite_data=args.overwrite_data)
+write_to_h5(args.input_path, args.h5_path,
+ h5path=args.target_path,
+ mode=mode,
+ overwrite_data=args.overwrite_data)
diff --git a/package/debian8/changelog b/package/debian8/changelog
new file mode 100644
index 0000000..763d069
--- /dev/null
+++ b/package/debian8/changelog
@@ -0,0 +1,22 @@
+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
new file mode 100644
index 0000000..668a363
--- /dev/null
+++ b/package/debian8/clean
@@ -0,0 +1 @@
+*.egg-info/* \ No newline at end of file
diff --git a/package/debian8/compat b/package/debian8/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/package/debian8/compat
@@ -0,0 +1 @@
+9
diff --git a/package/debian8/control b/package/debian8/control
new file mode 100644
index 0000000..6ffe66f
--- /dev/null
+++ b/package/debian8/control
@@ -0,0 +1,198 @@
+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,
+ cython-dbg,
+ cython3,
+ cython3-dbg,
+ libstdc++6-4.9-dbg,
+ libstdc++-4.9-dev,
+ libstdc++6,
+ debhelper (>=9.20150101+deb8u2),
+ dh-python,
+ python-all-dev,
+ python-all-dbg,
+ python-numpy,
+ python-numpy-dbg,
+ python-fabio,
+ python-fabio-dbg,
+ python-h5py,
+ python-h5py-dbg,
+ python-pyopencl,
+ python-pyopencl-dbg,
+ python-mako,
+ ipython,
+ ipython-qtconsole,
+ python-matplotlib,
+ python-matplotlib-dbg,
+ python-opengl,
+ python-pyqt5,
+ python-pyqt5-dbg,
+ python-pyqt5.qtopengl,
+ python-pyqt5.qtopengl-dbg,
+ python-scipy,
+ python-scipy-dbg,
+ python-sphinx,
+ python-sphinxcontrib.programoutput,
+ python-enum34,
+ python3-all-dev,
+ python3-all-dbg,
+ python3-numpy,
+ python3-numpy-dbg,
+ python3-fabio,
+ python3-fabio-dbg,
+ python3-h5py,
+ python3-h5py-dbg,
+ python3-pyopencl,
+ python3-pyopencl-dbg,
+ python3-mako,
+ ipython3,
+ ipython3-qtconsole,
+ python3-matplotlib,
+ python3-matplotlib-dbg,
+ python3-opengl,
+ python3-pyqt5,
+ python3-pyqt5-dbg,
+ python3-pyqt5.qtopengl,
+ python3-pyqt5.qtopengl-dbg,
+ python3-scipy,
+ python3-scipy-dbg,
+ python3-sphinx,
+ python3-sphinxcontrib.programoutput,
+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,
+ ipython,
+ ipython-qtconsole,
+ python-matplotlib,
+ python-opengl,
+ python-pyqt5,
+ python-pyqt5.qtopengl,
+ python-scipy,
+ python-six,
+ python-enum34,
+# Recommends:
+Suggests: python-rfoo
+Description: Toolbox for X-Ray data analysis - Python2 library
+ .
+ This is the Python 2 version of the package.
+
+Package: python-silx-dbg
+Architecture: any
+Section: debug
+Depends: ${misc:Depends},
+ ${python:Depends},
+ ${shlibs:Depends},
+ python-silx (= ${binary:Version}),
+ libstdc++6-4.9-dbg,
+ python-dbg,
+ python-numpy-dbg,
+ python-fabio-dbg,
+ python-h5py-dbg,
+ python-pyopencl-dbg,
+ python-mako,
+ ipython,
+ ipython-qtconsole,
+ python-matplotlib-dbg,
+ python-opengl,
+ python-pyqt5-dbg,
+ python-pyqt5.qtopengl-dbg,
+ python-scipy-dbg,
+ python-six,
+ python-enum34,
+Description: Toolbox for X-Ray data analysis - python2 debug
+ .
+ This package contains the extension built for the Python 2 debug
+ interpreter.
+
+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,
+ ipython3,
+ ipython3-qtconsole,
+ python3-matplotlib,
+ python3-opengl,
+ python3-pyqt5,
+ python3-pyqt5.qtopengl,
+ python3-scipy,
+ python3-six,
+# Recommends:
+# Suggests: python3-rfoo
+Description: Toolbox for X-Ray data analysis - Python3
+ .
+ This is the Python 3 version of the package.
+
+Package: python3-silx-dbg
+Architecture: any
+Section: debug
+Depends: ${misc:Depends},
+ ${python3:Depends},
+ ${shlibs:Depends},
+ python3-silx (= ${binary:Version}),
+ libstdc++6-4.9-dbg,
+ python3-dbg,
+ python3-numpy-dbg,
+ python3-fabio-dbg,
+ python3-h5py-dbg,
+ python3-pyopencl-dbg,
+ python3-mako,
+ ipython3,
+ ipython3-qtconsole,
+ python3-matplotlib-dbg,
+ python3-opengl,
+ python3-pyqt5-dbg,
+ python3-pyqt5.qtopengl-dbg,
+ python3-scipy-dbg,
+ python3-six,
+Description: Toolbox for X-Ray data analysis - Python3 debug
+ .
+ This package contains the extension built for the Python 3 debug
+ interpreter.
+
+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/gbp.conf b/package/debian8/gbp.conf
new file mode 100644
index 0000000..f68d262
--- /dev/null
+++ b/package/debian8/gbp.conf
@@ -0,0 +1,2 @@
+[DEFAULT]
+debian-branch = master \ No newline at end of file
diff --git a/package/debian8/python-silx-doc.doc-base b/package/debian8/python-silx-doc.doc-base
new file mode 100644
index 0000000..b290d8a
--- /dev/null
+++ b/package/debian8/python-silx-doc.doc-base
@@ -0,0 +1,9 @@
+Document: silx-manual
+Title: silx documentation manual
+Author: Jérôme Kieffer <jerome.kieffer@esrf.eu>
+Abstract: Toolbox for X-Ray data analysis
+Section: Science/Data Analysis
+
+Format: HTML
+Index: /usr/share/doc/python-silx-doc/html/index.html
+Files: /usr/share/doc/python-silx-doc/html/*
diff --git a/package/debian8/rules b/package/debian8/rules
new file mode 100755
index 0000000..98e59cd
--- /dev/null
+++ b/package/debian8/rules
@@ -0,0 +1,54 @@
+#!/usr/bin/make -f
+
+export PYBUILD_NAME=silx
+
+# 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
+ rm -rf debian/python-silx/usr/bin
+ rm -rf debian/python3-silx/usr/bin
+
+ # remove all py/pyc/egg-info files from dbg packages
+ find debian/python-silx-dbg/usr -type f \( -not -name "*.so" \) -delete
+ find debian/python3-silx-dbg/usr -type f \( -not -name "*.so" \) -delete
+
+ 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/source/format b/package/debian8/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/package/debian8/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/package/debian8/source/options b/package/debian8/source/options
new file mode 100644
index 0000000..6e88e49
--- /dev/null
+++ b/package/debian8/source/options
@@ -0,0 +1 @@
+extend-diff-ignore="^[^/]+\.egg-info/" \ No newline at end of file
diff --git a/package/debian8/watch b/package/debian8/watch
new file mode 100644
index 0000000..8972716
--- /dev/null
+++ b/package/debian8/watch
@@ -0,0 +1,5 @@
+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/changelog b/package/debian9/changelog
new file mode 100644
index 0000000..763d069
--- /dev/null
+++ b/package/debian9/changelog
@@ -0,0 +1,22 @@
+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/debian9/clean b/package/debian9/clean
new file mode 100644
index 0000000..668a363
--- /dev/null
+++ b/package/debian9/clean
@@ -0,0 +1 @@
+*.egg-info/* \ No newline at end of file
diff --git a/package/debian9/compat b/package/debian9/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/package/debian9/compat
@@ -0,0 +1 @@
+9
diff --git a/package/debian9/control b/package/debian9/control
new file mode 100644
index 0000000..f5bffde
--- /dev/null
+++ b/package/debian9/control
@@ -0,0 +1,125 @@
+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,
+ ipython,
+ ipython-qtconsole,
+ python-matplotlib,
+ python-opengl,
+ python-pyqt5,
+ python-pyqt5.qtopengl,
+ python-scipy,
+ python-sphinx,
+ python-sphinxcontrib.programoutput,
+ python-enum34,
+ python3-all-dev,
+ python3-numpy,
+ python3-fabio,
+ python3-h5py,
+ python3-pyopencl,
+ python3-mako,
+ ipython3,
+ ipython3-qtconsole,
+ python3-matplotlib,
+ python3-opengl,
+ python3-pyqt5,
+ python3-pyqt5.qtopengl,
+ python3-scipy,
+ python3-sphinx,
+ python3-sphinxcontrib.programoutput,
+ openstack-pkg-tools
+Standards-Version: 3.9.8
+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,
+ ipython,
+ ipython-qtconsole,
+ python-matplotlib,
+ python-opengl,
+ python-pyqt5,
+ python-pyqt5.qtopengl,
+ python-scipy,
+ python-six,
+ python-enum34,
+# 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,
+ ipython3,
+ ipython3-qtconsole,
+ python3-matplotlib,
+ python3-opengl,
+ python3-pyqt5,
+ python3-pyqt5.qtopengl,
+ python3-scipy,
+ 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/debian9/gbp.conf b/package/debian9/gbp.conf
new file mode 100644
index 0000000..f68d262
--- /dev/null
+++ b/package/debian9/gbp.conf
@@ -0,0 +1,2 @@
+[DEFAULT]
+debian-branch = master \ No newline at end of file
diff --git a/package/debian9/python-silx-doc.doc-base b/package/debian9/python-silx-doc.doc-base
new file mode 100644
index 0000000..b290d8a
--- /dev/null
+++ b/package/debian9/python-silx-doc.doc-base
@@ -0,0 +1,9 @@
+Document: silx-manual
+Title: silx documentation manual
+Author: Jérôme Kieffer <jerome.kieffer@esrf.eu>
+Abstract: Toolbox for X-Ray data analysis
+Section: Science/Data Analysis
+
+Format: HTML
+Index: /usr/share/doc/python-silx-doc/html/index.html
+Files: /usr/share/doc/python-silx-doc/html/*
diff --git a/package/debian9/rules b/package/debian9/rules
new file mode 100755
index 0000000..9adbaf4
--- /dev/null
+++ b/package/debian9/rules
@@ -0,0 +1,50 @@
+#!/usr/bin/make -f
+
+export PYBUILD_NAME=silx
+
+# 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
+ 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/debian9/source/format b/package/debian9/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/package/debian9/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/package/debian9/source/options b/package/debian9/source/options
new file mode 100644
index 0000000..6e88e49
--- /dev/null
+++ b/package/debian9/source/options
@@ -0,0 +1 @@
+extend-diff-ignore="^[^/]+\.egg-info/" \ No newline at end of file
diff --git a/package/debian9/watch b/package/debian9/watch
new file mode 100644
index 0000000..8972716
--- /dev/null
+++ b/package/debian9/watch
@@ -0,0 +1,5 @@
+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/desktop/org.silx.SilxView.desktop b/package/desktop/org.silx.SilxView.desktop
new file mode 100644
index 0000000..577c0c8
--- /dev/null
+++ b/package/desktop/org.silx.SilxView.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Type=Application
+Encoding=UTF-8
+Name=silx Data Viewer
+Comment=HDF5 EDF SPEC Data Viewer
+Exec=silx view %F
+Terminal=false
+Icon=silx
+Categories=Education;Science;DataVisualization;Qt
diff --git a/package/desktop/silx.png b/package/desktop/silx.png
new file mode 100644
index 0000000..c3b639b
--- /dev/null
+++ b/package/desktop/silx.png
Binary files differ
diff --git a/package/desktop/silx.svg b/package/desktop/silx.svg
new file mode 100644
index 0000000..6d91541
--- /dev/null
+++ b/package/desktop/silx.svg
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="265.95999"
+ height="265.95999"
+ viewBox="0 0 265.95998 265.95999"
+ id="svg2"
+ xml:space="preserve"><defs
+ id="defs74" /><metadata
+ id="metadata4"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><g
+ transform="matrix(6.1124607,0,0,6.1124607,-9.0246964,-11.55017)"
+ id="g6"><polygon
+ points="32.301,35.732 31.56,35.929 29.495,28.237 38.689,29.693 39.026,32.153 33.188,36.185 "
+ id="polygon8"
+ style="fill:#5a5b5d" /><path
+ d="M 29.222,26.766 C 29.279,26.645 26.99,26.557 26.99,26.557 l -4.19,3.048 4.559,7.404 4.322,-1.051 -2.459,-9.192 z"
+ id="path10"
+ style="fill:#808285" /><path
+ d="m 24.472,1.89 c 0,0 -4.283,2.305 -4.828,2.497 -0.546,0.189 -4.033,3.568 -3.404,3.791 0.628,0.223 3.772,1.429 3.772,1.429 L 26.414,5.893 25.73,3.523 24.472,1.89"
+ id="path12"
+ style="fill:#dcddde" /><path
+ d="m 16.067,8.177 -1.303,1.925 c 0,0 -0.701,2.221 -1.167,3.446 -0.452,1.187 -1.11,2.632 -1.11,2.632 l 3.899,3.088 c 0,0 6.086,-2.344 5.914,-2.23 -0.171,0.114 -2.287,-7.432 -2.287,-7.432 L 16.067,8.177 z"
+ id="path14"
+ style="fill:#f8f8f9" /><path
+ d="M 13.61,15.472"
+ id="path16"
+ style="fill:#939598" /><polygon
+ points="15.439,27.157 11.453,23.67 9.931,19.558 8.219,24.074 7.666,28 12.923,33.159 "
+ id="polygon18"
+ style="fill:#dcddde" /><path
+ d="m 7.666,28 c 0,0 -0.799,1.539 -0.794,2.701 0,1.164 0.396,3.087 0.396,3.087 L 8.185,36.429 15.531,34.437 13.155,33.159 7.666,28 z"
+ id="path20"
+ style="fill:#d1d3d4" /><path
+ d="m 18.127,39.503 2.523,0.67 5.38,4.701 c 0,0 -8.99,0.824 -9.364,0.41 -0.371,-0.41 0.659,-2.008 0.088,-2.408 -0.571,-0.402 1.373,-3.373 1.373,-3.373 z"
+ id="path22"
+ style="fill:#58595b" /><path
+ d="m 15.439,27.157 c 0,0 -2.625,5.788 -2.516,6.001 0.218,0.431 2.607,1.278 2.607,1.278 l 8.371,-5.678 -2.844,-11.251 -3.067,1.22 c 0,0 -3.294,5.58 -3.122,5.347 0.171,-0.234 0.571,3.083 0.571,3.083 z"
+ id="path24"
+ style="fill:#d1d3d4" /><path
+ d="m 21.058,17.507 0.35,-0.081 1.83,-0.731 c 0,0 1.176,1.715 1.119,1.887 -0.059,0.171 2.766,1.713 2.766,1.713 0,0 -0.021,6.005 -0.251,6.261 -0.228,0.258 -2.969,2.203 -2.969,2.203 L 21.058,17.507 z"
+ id="path26"
+ style="fill:#808285" /><path
+ d="m 20.299,40.275 c 0,0 6.674,-1.889 6.847,-2.002 0.171,-0.119 8.016,2.002 8.016,2.002 l -1.33,0.883 h -1.07 l -2.747,2.402 -3.984,1.314 -5.732,-4.599 z"
+ id="path28"
+ style="fill:#333334" /><polygon
+ points="27.239,37.009 32.301,35.732 38.019,37.826 35.162,40.275 27.35,38.281 "
+ id="polygon30"
+ style="fill:#515254" /><path
+ d="m 32.647,35.91 6.379,-3.757 c 0,0 0.555,2.034 0.566,2.376 0.007,0.348 -1.573,3.297 -1.573,3.297 L 32.647,35.91 z"
+ id="path32"
+ style="fill:#464547" /><polygon
+ points="29.104,26.766 26.871,26.556 26.871,21.247 30.071,21.783 32.189,28.826 29.575,28.531 "
+ id="polygon34"
+ style="fill:#9fa1a4" /><path
+ d="m 30.071,21.783 0.207,-0.131 5.299,-3.419 2.685,5.348 c 0,0 -0.294,4.035 -0.01,3.918 0.282,-0.112 0.437,2.194 0.437,2.194 l -6.5,-0.867 -2.118,-7.043 z"
+ id="path36"
+ style="fill:#464547" /><path
+ d="M 26.031,5.748"
+ id="path38"
+ style="fill:#939598" /><polygon
+ points="30.887,8.967 31.56,10.979 32.49,12.237 32.989,14.524 26.241,12.581 26.241,5.893 27.498,6.494 29.285,8.156 30.071,8.177 "
+ id="polygon40"
+ style="fill:#6d6e71" /><polygon
+ points="35.578,18.233 32.989,14.524 29.575,15.381 24.156,18.583 26.708,21.247 26.974,21.783 30.071,21.783 "
+ id="polygon42"
+ style="fill:#5a5b5d" /><path
+ d="m 26.241,5.893 c 0,0 -6.516,4 -6.229,3.714 0.025,-0.022 1.757,7.32 2.287,7.432 0.458,0.096 0.458,0.096 0.458,0.096 l 1.599,1.448 1.436,-4.059 0.449,-1.943 V 5.893 z"
+ id="path44"
+ style="fill:#939598" /><path
+ d="m 8.185,36.429 7.579,-2.156 2.364,5.23 -1.465,2.957 0.264,0.75 -0.259,2.074 -2.654,-1.322 -1.545,-1.313 -1.486,-1.943 c 0,0 -1.011,-0.891 -1.028,-1.203 -0.022,-0.31 -1.77,-3.074 -1.77,-3.074 z"
+ id="path46"
+ style="fill:#808285" /><polygon
+ points="12.487,16.18 10.696,17.753 9.931,19.558 11.453,23.67 15.439,27.157 14.869,24.074 17.991,18.727 16.386,19.268 "
+ id="polygon48"
+ style="fill:#ededee" /><polygon
+ points="26.241,12.581 25.792,14.524 24.356,18.583 29.575,15.381 32.989,14.524 "
+ id="polygon50"
+ style="fill:#464547" /><path
+ d="M 22.303,28.826"
+ id="path52"
+ style="fill:#a7a9ac" /><polygon
+ points="31.022,42.675 32.148,39.503 39.094,35.732 38.019,37.826 33.832,41.158 "
+ id="polygon54" /><polygon
+ points="20.649,40.173 18.127,39.503 15.763,34.273 22.682,29.605 27.757,37.009 27.35,38.281 27.35,38.281 "
+ id="polygon56"
+ style="fill:#515254" /><path
+ d="M 11.114,33.878"
+ id="path58"
+ style="fill:#939598" /><path
+ d="m 18.264,25.999 c 0,0.965 -0.465,1.451 -1.394,1.457 l -4.407,-0.009 c -0.978,0.012 -1.469,0.259 -1.475,0.742 0.012,0.458 0.507,0.685 1.484,0.678 h 1.421 c 2.914,0.013 4.377,1.237 4.389,3.674 0,2.371 -1.46,3.563 -4.38,3.574 H 9.549 C 8.59,36.11 8.11,35.637 8.11,34.696 8.116,33.737 8.592,33.262 9.54,33.276 l 4.362,-0.027 c 0.977,-0.006 1.466,-0.254 1.466,-0.742 0,-0.496 -0.495,-0.74 -1.484,-0.734 h -1.42 C 9.562,31.767 8.111,30.573 8.111,28.19 v -0.045 c 0,-2.389 1.451,-3.587 4.353,-3.593 h 4.353 c 0.958,0.005 1.441,0.488 1.447,1.447 z"
+ id="path60"
+ style="fill:#f7941e;stroke:#ffffff;stroke-width:0.30000001;stroke-miterlimit:10" /><path
+ d="m 20.37,24.288 c 0.923,0.006 1.394,0.497 1.411,1.471 v 8.955 c -0.011,0.986 -0.476,1.488 -1.394,1.508 -0.923,-0.025 -1.385,-0.527 -1.385,-1.508 v -8.946 c -0.006,-0.987 0.45,-1.48 1.368,-1.48 z"
+ id="path62"
+ style="fill:#f7941e;stroke:#ffffff;stroke-width:0.30000001;stroke-miterlimit:10" /><circle
+ cx="20.290001"
+ cy="20.566999"
+ r="1.689"
+ id="circle64"
+ style="fill:#f7941e;stroke:#ffffff;stroke-width:0.30000001;stroke-miterlimit:10" /><path
+ d="m 27.838,24.594 c 0.559,-0.001 0.979,0.115 1.263,0.351 0.283,0.236 1.279,1.245 2.986,3.026 1.727,-1.82 2.731,-2.847 3.014,-3.081 0.282,-0.233 0.702,-0.352 1.261,-0.353 0.545,-10e-4 0.964,0.119 1.257,0.36 0.28,0.229 0.421,0.574 0.422,1.035 v 0.063 c 0.001,0.515 -0.295,0.997 -0.887,1.445 l -2.841,2.923 2.865,2.88 c 0.587,0.477 0.882,0.938 0.883,1.379 0,0.031 -0.004,0.064 -0.011,0.1 v 0.119 c 0.001,0.43 -0.134,0.756 -0.405,0.98 -0.307,0.25 -0.732,0.369 -1.277,0.357 -0.572,0.008 -1.003,-0.117 -1.293,-0.373 -0.291,-0.258 -1.269,-1.258 -2.935,-3.004 -1.692,1.783 -2.681,2.801 -2.966,3.053 -0.285,0.252 -0.715,0.379 -1.287,0.381 h -0.042 c -0.523,0.002 -0.932,-0.117 -1.226,-0.352 -0.28,-0.223 -0.42,-0.551 -0.421,-0.98 v -0.117 c -0.007,-0.037 -0.01,-0.07 -0.01,-0.1 -10e-4,-0.443 0.291,-0.9 0.876,-1.375 L 30.042,30.408 27.062,27.5 C 26.467,27.053 26.17,26.572 26.168,26.058 v -0.063 c -10e-4,-0.46 0.138,-0.806 0.417,-1.036 0.29,-0.242 0.709,-0.364 1.253,-0.365 z"
+ id="path66"
+ style="fill:#f7941e;stroke:#ffffff;stroke-width:0.30000001;stroke-miterlimit:10" /><path
+ d="m 25.549,35.16 c 0,0.551 -0.45,1 -1,1 h -0.845 c -0.55,0 -1,-0.449 -1,-1 V 15.277 c 0,-0.55 0.45,-1 1,-1 h 0.845 c 0.55,0 1,0.45 1,1 V 35.16 z"
+ id="path68"
+ style="fill:#f7941e" /><path
+ d="m 25.549,35.16 c 0,0.551 -0.45,1 -1,1 h -0.845 c -0.55,0 -1,-0.449 -1,-1 V 15.277 c 0,-0.55 0.45,-1 1,-1 h 0.845 c 0.55,0 1,0.45 1,1 V 35.16 z"
+ id="path70"
+ style="fill:none;stroke:#ffffff;stroke-width:0.30000001;stroke-miterlimit:10" /></g></svg> \ No newline at end of file
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..d3c36e3
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,7 @@
+# List of silx development dependencies
+# Those ARE NOT required for installation, at runtime or to build from source (except for the doc)
+
+Cython >= 0.21.1 # To regenerate .c/.cpp files from .pyx
+Sphinx # To build the documentation in doc/
+lxml # For test coverage in run_test.py
+coverage # For test coverage in run_test.py
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..9e46ba1
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,29 @@
+# List all dependencies of silx
+# Requires pip >= 8.0
+
+--trusted-host www.silx.org
+--find-links http://www.silx.org/pub/wheelhouse/
+--only-binary numpy,h5py,scipy,PyQt4,PyQt5,PySide
+
+numpy >= 1.8
+fabio >= 0.4 # For silx.io
+h5py # For silx.io
+pyopencl # For silx.opencl
+Mako # For pyopencl reduction
+ipython # For silx.gui.console
+qtconsole # For silx.gui.console
+matplotlib >= 1.2.0 # For silx.gui.plot
+PyOpenGL # For silx.gui.plot3d
+# PyQt4, PyQt5 or PySide # For silx.gui
+# scipy # For silx.math.fit demo, silx.image.sift demo, silx.image.sift.test
+
+# Try to install a Qt binding from a wheel
+# This is no available for all configurations
+
+# Require PyQt when wheel is available
+PyQt5; python_version >= '3.5'
+PyQt4; sys_platform == 'win32' and python_version == '2.7' # From silx.org
+PyQt4; sys_platform == 'darwin' and python_version == '2.7' # From silx.org
+
+# Require scipy when wheel is available
+scipy; sys_platform != 'win32'
diff --git a/run_tests.py b/run_tests.py
index f314e6a..f01ea84 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -32,7 +32,7 @@ Test coverage dependencies: coverage, lxml.
"""
__authors__ = ["Jérôme Kieffer", "Thomas Vincent"]
-__date__ = "19/04/2017"
+__date__ = "03/08/2017"
__license__ = "MIT"
import distutils.util
@@ -44,7 +44,38 @@ import time
import unittest
-logging.basicConfig(level=logging.WARNING)
+class StreamHandlerUnittestReady(logging.StreamHandler):
+ """The unittest class TestResult redefine sys.stdout/err to capture
+ stdout/err from tests and to display them only when a test fail.
+ This class allow to use unittest stdout-capture by using the last sys.stdout
+ and not a cached one.
+ """
+
+ def emit(self, record):
+ """
+ :type record: logging.LogRecord
+ """
+ self.stream = sys.stderr
+ super(StreamHandlerUnittestReady, self).emit(record)
+
+ def flush(self):
+ pass
+
+
+def createBasicHandler():
+ """Create the handler using the basic configuration"""
+ hdlr = StreamHandlerUnittestReady()
+ fs = logging.BASIC_FORMAT
+ dfs = None
+ fmt = logging.Formatter(fs, dfs)
+ hdlr.setFormatter(fmt)
+ return hdlr
+
+
+# Use an handler compatible with unittests, else use_buffer is not working
+logging.root.addHandler(createBasicHandler())
+logging.captureWarnings(True)
+
logger = logging.getLogger("run_tests")
logger.setLevel(logging.WARNING)
@@ -100,6 +131,17 @@ PROJECT_NAME = get_project_name(PROJECT_DIR)
logger.info("Project name: %s", PROJECT_NAME)
+class TextTestResultWithSkipList(unittest.TextTestResult):
+ """Override default TextTestResult to display list of skipped tests at the
+ end
+ """
+
+ def printErrors(self):
+ unittest.TextTestResult.printErrors(self)
+ # Print skipped tests at the end
+ self.printErrorList("SKIPPED", self.skipped)
+
+
class ProfileTextTestResult(unittest.TextTestRunner.resultclass):
def __init__(self, *arg, **kwarg):
@@ -228,9 +270,10 @@ WITH_GL_TEST=False to disable tests using OpenGL
parser = ArgumentParser(description='Run the tests.',
epilog=epilog)
-parser.add_argument("-i", "--insource",
- action="store_true", dest="insource", default=False,
- help="Use the build source and not the installed version")
+parser.add_argument("--installed",
+ action="store_true", dest="installed", default=False,
+ help=("Test the installed version instead of" +
+ "building from the source"))
parser.add_argument("-c", "--coverage", dest="coverage",
action="store_true", default=False,
help=("Report code coverage" +
@@ -267,14 +310,17 @@ sys.argv = [sys.argv[0]]
test_verbosity = 1
+use_buffer = True
if options.verbose == 1:
logging.root.setLevel(logging.INFO)
logger.info("Set log level: INFO")
test_verbosity = 2
+ use_buffer = False
elif options.verbose > 1:
logging.root.setLevel(logging.DEBUG)
logger.info("Set log level: DEBUG")
test_verbosity = 2
+ use_buffer = False
if not options.gui:
os.environ["WITH_QT_TEST"] = "False"
@@ -304,6 +350,14 @@ if options.qt_binding:
binding = options.qt_binding.lower()
if binding == "pyqt4":
logger.info("Force using PyQt4")
+ if sys.version < "3.0.0":
+ try:
+ import sip
+
+ sip.setapi("QString", 2)
+ sip.setapi("QVariant", 2)
+ except:
+ logger.warning("Cannot set sip API")
import PyQt4.QtCore # noqa
elif binding == "pyqt5":
logger.info("Force using PyQt5")
@@ -322,16 +376,14 @@ if (os.path.dirname(os.path.abspath(__file__)) ==
# import module
-if not options.insource:
+if options.installed: # Use installed version
try:
module = importer(PROJECT_NAME)
except:
- logger.warning(
- "%s missing, using built (i.e. not installed) version",
+ raise ImportError(
+ "%s not installed: Cannot run tests on installed version" %
PROJECT_NAME)
- options.insource = True
-
-if options.insource:
+else: # Use built source
build_dir = build_project(PROJECT_NAME, PROJECT_DIR)
sys.path.insert(0, build_dir)
@@ -346,8 +398,11 @@ PROJECT_PATH = module.__path__[0]
# Run the tests
runnerArgs = {}
runnerArgs["verbosity"] = test_verbosity
+runnerArgs["buffer"] = use_buffer
if options.memprofile:
runnerArgs["resultclass"] = ProfileTextTestResult
+else:
+ runnerArgs["resultclass"] = TextTestResultWithSkipList
runner = unittest.TextTestRunner(**runnerArgs)
logger.warning("Test %s %s from %s",
@@ -368,17 +423,14 @@ else:
test_suite.addTest(
unittest.defaultTestLoader.loadTestsFromNames(options.test_name))
+# Display the result when using CTRL-C
+unittest.installHandler()
result = runner.run(test_suite)
-for test, reason in result.skipped:
- logger.warning('Skipped %s (%s): %s',
- test.id(), test.shortDescription() or '', reason)
if result.wasSuccessful():
- logger.info("Test suite succeeded")
exit_status = 0
else:
- logger.warning("Test suite failed")
exit_status = 1
diff --git a/setup.cfg b/setup.cfg
index 861a9f5..8bfd5a1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,4 @@
[egg_info]
tag_build =
tag_date = 0
-tag_svn_revision = 0
diff --git a/setup.py b/setup.py
index 852c48d..7593cf5 100644
--- a/setup.py
+++ b/setup.py
@@ -25,7 +25,7 @@
# ###########################################################################*/
__authors__ = ["Jérôme Kieffer", "Thomas Vincent"]
-__date__ = "04/05/2017"
+__date__ = "02/10/2017"
__license__ = "MIT"
@@ -106,7 +106,6 @@ classifiers = ["Development Status :: 4 - Beta",
"Intended Audience :: Education",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: MIT License",
- "License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)",
"Natural Language :: English",
"Operating System :: MacOS",
"Operating System :: Microsoft :: Windows",
@@ -115,6 +114,7 @@ classifiers = ["Development Status :: 4 - Beta",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Scientific/Engineering :: Physics",
"Topic :: Software Development :: Libraries :: Python Modules",
@@ -152,7 +152,7 @@ class PyTest(Command):
def run(self):
import subprocess
- errno = subprocess.call([sys.executable, 'run_tests.py', '-i'])
+ errno = subprocess.call([sys.executable, 'run_tests.py'])
if errno != 0:
raise SystemExit(errno)
@@ -441,7 +441,6 @@ class Build(_build):
logger.warning(msg)
use_cython = "no"
-
# Remove attribute used by distutils parsing
# use 'use_cython' and 'force_cython' instead
del self.no_cython
@@ -461,7 +460,7 @@ class BuildExt(build_ext):
COMPILE_ARGS_CONVERTER = {'-fopenmp': '/openmp'}
- LINK_ARGS_CONVERTER = {'-fopenmp': ' '}
+ LINK_ARGS_CONVERTER = {'-fopenmp': ''}
description = 'Build silx extensions'
@@ -530,7 +529,63 @@ class BuildExt(build_ext):
ext.extra_link_args = [self.LINK_ARGS_CONVERTER.get(f, f)
for f in ext.extra_link_args]
+ def is_debug_interpreter(self):
+ """
+ Returns true if the script is executed with a debug interpreter.
+
+ It looks to be a non-standard code. It is not working for Windows and
+ Mac. But it have to work at least for Debian interpreters.
+
+ :rtype: bool
+ """
+ if sys.version_info >= (3, 0):
+ # It is normalized on Python 3
+ # But it is not available on Windows CPython
+ if hasattr(sys, "abiflags"):
+ return "d" in sys.abiflags
+ else:
+ # It's a Python 2 interpreter
+ # pydebug is not available on Windows/Mac OS interpreters
+ if hasattr(sys, "pydebug"):
+ return sys.pydebug
+
+ # We can't know if we uses debug interpreter
+ return False
+
+ def patch_compiler(self):
+ """
+ Patch the compiler to:
+ - always compile extensions with debug symboles (-g)
+ - only compile asserts in debug mode (-DNDEBUG)
+
+ Plus numpy.distutils/setuptools/distutils inject a lot of duplicated
+ flags. This function tries to clean up default debug options.
+ """
+ build_obj = self.distribution.get_command_obj("build")
+ if build_obj.debug:
+ debug_mode = build_obj.debug
+ else:
+ # Force debug_mode also when it uses python-dbg
+ # It is needed for Debian packaging
+ debug_mode = self.is_debug_interpreter()
+
+ if self.compiler.compiler_type == "unix":
+ args = list(self.compiler.compiler_so)
+ # clean up debug flags -g is included later in another way
+ must_be_cleaned = ["-DNDEBUG", "-g"]
+ args = filter(lambda x: x not in must_be_cleaned, args)
+ args = list(args)
+
+ # always insert symbols
+ args.append("-g")
+ # only strip asserts in release mode
+ if not debug_mode:
+ args.append('-DNDEBUG')
+ # patch options
+ self.compiler.compiler_so = list(args)
+
def build_extensions(self):
+ self.patch_compiler()
for ext in self.extensions:
self.patch_extension(ext)
build_ext.build_extensions(self)
@@ -654,15 +709,18 @@ def get_project_configuration(dry_run):
setup_requires = ["setuptools", "numpy"]
package_data = {
+ # Resources files for silx
'silx.resources': [
- # Add here all resources files
+ 'gui/logo/*.png',
+ 'gui/logo/*.svg',
'gui/icons/*.png',
'gui/icons/*.svg',
'gui/icons/*.mng',
'gui/icons/*.gif',
- 'gui/icons/animated/*.png',
+ 'gui/icons/*/*.png',
'opencl/*.cl',
- 'opencl/sift/*.cl']
+ 'opencl/sift/*.cl',
+ 'gui/colormaps/*.npy'],
}
entry_points = {
diff --git a/silx.egg-info/PKG-INFO b/silx.egg-info/PKG-INFO
index 7a6c11c..7649c9a 100644
--- a/silx.egg-info/PKG-INFO
+++ b/silx.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: silx
-Version: 0.5.0
+Version: 0.6.0
Summary: Software library for X-Ray data analysis
Home-page: https://github.com/silx-kit/silx
Author: data analysis unit
@@ -10,20 +10,30 @@ Description:
silx toolkit
============
- 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 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 :
+ The current version provides:
- * reading `HDF5 <https://www.hdfgroup.org/HDF5/>`_ file format (with support of `SPEC <https://certif.com/spec.html>`_ file format)
+ * reading `HDF5 <https://www.hdfgroup.org/HDF5/>`_ file format (with support of
+ `SPEC <https://certif.com/spec.html>`_ file format and
+ `FabIO <http://www.silx.org/doc/fabio/dev/getting_started.html#list-of-file-formats-that-fabio-can-read-and-write>`_
+ images)
* histogramming
* fitting
- * 1D and 2D visualization using multiple backends (matplotlib or OpenGL)
- * image plot widget with a set of associated tools (See `changelog file <https://github.com/silx-kit/silx/blob/master/CHANGELOG.rst>`_).
- * 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.
+ * 1D and 2D visualization widgets using multiple backends (matplotlib or OpenGL)
+ * an OpenGL-based widget to display 3D scalar field with isosurface and cutting plane
+ * an image plot widget with a set of associated tools
+ * a unified browser for HDF5, SPEC and image file formats supporting inspection and
+ visualization of n-dimensional datasets.
+ * a unified viewer (*silx view filename*) for HDF5, SPEC and image file formats
+ * a unified converter to HDF5 format (*silx convert filename*)
+ * median filters on images (C and OpenCL implementations)
* image alignement (sift - OpenCL implementation)
+ * filtered backprojection and forward projection for tomography
Installation
------------
@@ -31,21 +41,36 @@ Description:
To install silx, run::
pip install silx
+
+ Or with Anaconda on Linux and MacOS::
+
+ conda install silx -c conda-forge
To install silx locally, run::
pip install silx --user
- On Linux, to install silx with pip, you must install numpy first. Unofficial packages for different distributions are available :
+ Unofficial packages for different distributions are available :
- Unofficial Debian8 packages are available at http://www.silx.org/pub/debian/
- CentOS 7 rpm packages are provided by Max IV at the following url: http://pubrepo.maxiv.lu.se/rpm/el7/x86_64/
- Fedora 23 rpm packages are provided by Max IV at http://pubrepo.maxiv.lu.se/rpm/fc23/x86_64/
- Arch Linux (AUR) packages are also available: https://aur.archlinux.org/packages/python-silx
- On Windows, pre-compiled binaries (aka Python wheels) are available for Python 2.7, 3.5 and 3.6.
+ Beside this, we provide a certain number of wheels (pre-compiled binary packages) to be installed
+ onto a pre-existing Python installation:
+
+ - On Windows, binary wheels are available for Python 2.7, 3.5 and 3.6.
+ - On MacOS, binary wheels are available for Python 2.7, 3.4, 3.5 and 3.6.
+ - On Linux, manylinux1 binary wheels are available for Python 2.7, 3.4, 3.5 and 3.6.
+
+ Those builds are made from "up-date" systems at the time of the release, i.e. they use
+ the latest stable version of numpy (and cython).
+ Hence your system should use a fairly recent version of numpy to be compatible with silx.
+ This can be achieved simply by::
+
+ pip install numpy --upgrade
- On Mac OS X, pre-compiled binaries (aka Python wheels) are available for Python 2.7.
The latest development version can be obtained from the git repository::
@@ -65,19 +90,28 @@ Description:
* `matplotlib <http://matplotlib.org/>`_ for the silx.gui.plot package
* `PyOpenGL <http://pyopengl.sourceforge.net/>`_ for the silx.gui.plot3d package
- Most modules and functions dealing with `HDF5 <https://www.hdfgroup.org/HDF5/>`_ input/output depend on the following extra package:
+ Most modules and functions dealing with `HDF5 <https://www.hdfgroup.org/HDF5/>`_ input/output depend on:
+
* `h5py <http://www.h5py.org/>`_
- * `ipython <https://ipython.org/>`_ and `qtconsole <https://pypi.python.org/pypi/qtconsole>`_ is required by silx.gui.console.py
+ Parallel algorithms depend on:
+
+ * `PyOpenCL <https://documen.tician.de/pyopencl/>`_
+
+ The console widgets depend on:
+
+ * `ipython <https://ipython.org/>`_
+ * `qtconsole <https://pypi.python.org/pypi/qtconsole>`_
+
Supported platforms: Linux, Windows, Mac OS X.
Documentation
-------------
- Documentation of releases is available at https://pythonhosted.org/silx/
+ Documentation of latest release is available at http://www.silx.org/doc/silx/latest/
- Latest documentation (nightly build) is available at http://www.silx.org/doc/silx/
+ Documentation of previous releases and nightly build is available at http://www.silx.org/doc/silx/
To build the documentation from the source (requires `Sphinx <http://www.sphinx-doc.org>`_), run::
@@ -101,21 +135,28 @@ Description:
Examples
--------
- Some examples are available in the source code repository. For example::
-
- python examples/{exampleName.py}
+ Some examples of sample code using silx are provided with the
+ `silx documentation <http://www.silx.org/doc/silx/dev/sample_code/index.html>`_.
License
-------
- The source code of silx is licensed under the MIT and LGPL licenses.
- See the `copyright file <https://github.com/silx-kit/silx/blob/master/copyright>`_ for details.
+ The source code of silx is licensed under the MIT license.
+ See the `LICENSE <https://github.com/silx-kit/silx/blob/master/LICENSE>`_ and `copyright <https://github.com/silx-kit/silx/blob/master/copyright>`_ files for details.
+
+ Citation
+ --------
+
+ silx releases can be cited by their DOI on Zenodo: |DOI:10.5281/zenodo.1000472|
.. |Travis Status| image:: https://travis-ci.org/silx-kit/silx.svg?branch=master
:target: https://travis-ci.org/silx-kit/silx
.. |Appveyor Status| image:: https://ci.appveyor.com/api/projects/status/qgox9ei0wxwfagrb/branch/master?svg=true
:target: https://ci.appveyor.com/project/ESRF/silx
+ .. |DOI:10.5281/zenodo.1000472| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.1000472.svg
+ :target: https://doi.org/10.5281/zenodo.1000472
+
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
@@ -126,7 +167,6 @@ Classifier: Environment :: X11 Applications :: Qt
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
-Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)
Classifier: Natural Language :: English
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
@@ -135,6 +175,7 @@ Classifier: Programming Language :: Cython
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
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 6c9b73a..9918323 100644
--- a/silx.egg-info/SOURCES.txt
+++ b/silx.egg-info/SOURCES.txt
@@ -1,7 +1,11 @@
CHANGELOG.rst
+LICENSE
MANIFEST.in
README.rst
+build-deb.sh
copyright
+requirements-dev.txt
+requirements.txt
run_tests.py
setup.py
stdeb.cfg
@@ -17,6 +21,7 @@ doc/source/virtualenv.rst
doc/source/Tutorials/array_widget.rst
doc/source/Tutorials/fit.rst
doc/source/Tutorials/fitconfig.rst
+doc/source/Tutorials/io.rst
doc/source/Tutorials/specfile_to_hdf5.rst
doc/source/Tutorials/Sift/sift.rst
doc/source/Tutorials/img/arraywidget3D_0.png
@@ -31,6 +36,7 @@ doc/source/Tutorials/img/fitwidget2.png
doc/source/Tutorials/img/fitwidget3.png
doc/source/Tutorials/img/fitwidget4.png
doc/source/Tutorials/img/fitwidget5.png
+doc/source/Tutorials/img/silx_view_edf.png
doc/source/Tutorials/img/stripbg_plot1.png
doc/source/Tutorials/img/stripbg_plot2.png
doc/source/description/index.rst
@@ -42,8 +48,11 @@ doc/source/description/img/sift_bench_gpu0.png
doc/source/description/img/sift_bench_gpu_kp.png
doc/source/description/img/sift_bench_gpu_res.png
doc/source/description/img/sift_dog1.png
+doc/source/description/img/sift_frame_ROI.png
doc/source/description/img/sift_match1.png
+doc/source/description/img/sift_match2.png
doc/source/description/img/sift_orientation.png
+doc/source/ext/sphinxext-archive.py
doc/source/img/silx.ico
doc/source/img/silx_large.png
doc/source/img/silx_small.png
@@ -51,6 +60,7 @@ doc/source/modules/index.rst
doc/source/modules/resources.rst
doc/source/modules/gui/console.rst
doc/source/modules/gui/designer.rst
+doc/source/modules/gui/gallery.rst
doc/source/modules/gui/icons.rst
doc/source/modules/gui/index.rst
doc/source/modules/gui/qt.rst
@@ -61,13 +71,15 @@ doc/source/modules/gui/data/dataviewerframe.rst
doc/source/modules/gui/data/index.rst
doc/source/modules/gui/data/numpyaxesselector.rst
doc/source/modules/gui/data/textformatter.rst
+doc/source/modules/gui/data/img/ArrayTableWidget.png
doc/source/modules/gui/data/img/DataViewer.png
doc/source/modules/gui/data/img/DataViewerFrame.png
doc/source/modules/gui/data/img/NumpyAxesSelector.png
doc/source/modules/gui/fit/backgroundwidget.rst
doc/source/modules/gui/fit/fitwidget.rst
doc/source/modules/gui/fit/index.rst
-doc/source/modules/gui/fit/img/bgwidget.png
+doc/source/modules/gui/fit/img/BackgroundDialog.png
+doc/source/modules/gui/fit/img/FitWidget.png
doc/source/modules/gui/hdf5/examples_hdf5widget.rst
doc/source/modules/gui/hdf5/getting_started.rst
doc/source/modules/gui/hdf5/h5node.rst
@@ -78,20 +90,34 @@ doc/source/modules/gui/hdf5/index.rst
doc/source/modules/gui/hdf5/nexussortfilterproxymodel.rst
doc/source/modules/gui/hdf5/img/Hdf5Example.png
doc/source/modules/gui/hdf5/img/Hdf5TreeView.png
+doc/source/modules/gui/img/IPythonDockWidget.png
+doc/source/modules/gui/img/IPythonWidget.png
+doc/source/modules/gui/plot/colormap.rst
+doc/source/modules/gui/plot/compleximageview.rst
doc/source/modules/gui/plot/dev.rst
doc/source/modules/gui/plot/getting_started.rst
doc/source/modules/gui/plot/imageview.rst
doc/source/modules/gui/plot/index.rst
doc/source/modules/gui/plot/items.rst
-doc/source/modules/gui/plot/plot.rst
-doc/source/modules/gui/plot/plotactions.rst
-doc/source/modules/gui/plot/plotactions_examples.rst
doc/source/modules/gui/plot/plottools.rst
doc/source/modules/gui/plot/plotwidget.rst
doc/source/modules/gui/plot/plotwindow.rst
+doc/source/modules/gui/plot/printpreviewtoolbutton.rst
doc/source/modules/gui/plot/profile.rst
doc/source/modules/gui/plot/roi.rst
doc/source/modules/gui/plot/stackview.rst
+doc/source/modules/gui/plot/actions/control.rst
+doc/source/modules/gui/plot/actions/examples.rst
+doc/source/modules/gui/plot/actions/fit.rst
+doc/source/modules/gui/plot/actions/histogram.rst
+doc/source/modules/gui/plot/actions/index.rst
+doc/source/modules/gui/plot/actions/io.rst
+doc/source/modules/gui/plot/actions/medfilt.rst
+doc/source/modules/gui/plot/actions/img/fftAction0.png
+doc/source/modules/gui/plot/actions/img/fftAction1.png
+doc/source/modules/gui/plot/actions/img/shiftAction0.png
+doc/source/modules/gui/plot/actions/img/shiftAction3.png
+doc/source/modules/gui/plot/img/ComplexImageView.png
doc/source/modules/gui/plot/img/ImageView.png
doc/source/modules/gui/plot/img/LimitsToolBar.png
doc/source/modules/gui/plot/img/Plot1D.png
@@ -103,16 +129,13 @@ doc/source/modules/gui/plot/img/StackView.png
doc/source/modules/gui/plot/img/StackViewMainWindow.png
doc/source/modules/gui/plot/img/colorScale.png
doc/source/modules/gui/plot/img/colorScaleBar.png
-doc/source/modules/gui/plot/img/fftAction0.png
-doc/source/modules/gui/plot/img/fftAction1.png
doc/source/modules/gui/plot/img/linearColorbar.png
doc/source/modules/gui/plot/img/logColorbar.png
doc/source/modules/gui/plot/img/netCounts.png
doc/source/modules/gui/plot/img/plot_and_backend.png
+doc/source/modules/gui/plot/img/printPreviewMultiPlot.png
doc/source/modules/gui/plot/img/rawCounts.png
doc/source/modules/gui/plot/img/roiwidget.png
-doc/source/modules/gui/plot/img/shiftAction0.png
-doc/source/modules/gui/plot/img/shiftAction3.png
doc/source/modules/gui/plot/img/tickbar.png
doc/source/modules/gui/plot3d/actions.rst
doc/source/modules/gui/plot3d/dev.rst
@@ -123,7 +146,7 @@ doc/source/modules/gui/plot3d/plot3dwindow.rst
doc/source/modules/gui/plot3d/scalarfieldview.rst
doc/source/modules/gui/plot3d/scene.rst
doc/source/modules/gui/plot3d/sfviewparamtree.rst
-doc/source/modules/gui/plot3d/toolbars.rst
+doc/source/modules/gui/plot3d/tools.rst
doc/source/modules/gui/plot3d/utils.rst
doc/source/modules/gui/plot3d/viewer3dvolume_example.rst
doc/source/modules/gui/plot3d/img/Plot3DWidget.png
@@ -133,15 +156,28 @@ doc/source/modules/gui/plot3d/img/ScalarFieldView.png
doc/source/modules/gui/widgets/framebrowser.rst
doc/source/modules/gui/widgets/index.rst
doc/source/modules/gui/widgets/periodictable.rst
+doc/source/modules/gui/widgets/printpreview.rst
doc/source/modules/gui/widgets/tablewidget.rst
doc/source/modules/gui/widgets/threadpoolpushbutton.rst
doc/source/modules/gui/widgets/waitingpushbutton.rst
+doc/source/modules/gui/widgets/img/FrameBrowser.png
+doc/source/modules/gui/widgets/img/HorizontalSliderWithBrowser.png
+doc/source/modules/gui/widgets/img/PeriodicCombo.png
+doc/source/modules/gui/widgets/img/PeriodicList.png
+doc/source/modules/gui/widgets/img/PeriodicTable.png
+doc/source/modules/gui/widgets/img/TableWidget.png
+doc/source/modules/gui/widgets/img/ThreadPoolPushButton.png
+doc/source/modules/gui/widgets/img/WaitingPushButton.png
+doc/source/modules/image/backprojection.rst
doc/source/modules/image/bilinear.rst
doc/source/modules/image/index.rst
doc/source/modules/image/medianfilter.rst
+doc/source/modules/image/projection.rst
+doc/source/modules/image/reconstruction.rst
doc/source/modules/image/shapes.rst
doc/source/modules/image/sift.rst
doc/source/modules/io/configdict.rst
+doc/source/modules/io/convert.rst
doc/source/modules/io/dictdump.rst
doc/source/modules/io/index.rst
doc/source/modules/io/nxdata.rst
@@ -149,7 +185,6 @@ doc/source/modules/io/octaveh5.rst
doc/source/modules/io/specfile.rst
doc/source/modules/io/specfilewrapper.rst
doc/source/modules/io/spech5.rst
-doc/source/modules/io/spectoh5.rst
doc/source/modules/io/utils.rst
doc/source/modules/math/combo.rst
doc/source/modules/math/histogram.rst
@@ -170,20 +205,70 @@ doc/source/modules/utils/decorators.rst
doc/source/modules/utils/html.rst
doc/source/modules/utils/index.rst
doc/source/modules/utils/weakref.rst
+doc/source/sample_code/index.rst
+doc/source/sample_code/img/animatedicons.png
+doc/source/sample_code/img/customHdf5TreeModel.png
+doc/source/sample_code/img/fftPlotAction.png
+doc/source/sample_code/img/hdf5widget.png
+doc/source/sample_code/img/icons.png
+doc/source/sample_code/img/imageview.png
+doc/source/sample_code/img/periodicTable.png
+doc/source/sample_code/img/plotContextMenu.png
+doc/source/sample_code/img/plotItemsSelector.png
+doc/source/sample_code/img/plotLimits.png
+doc/source/sample_code/img/plotUpdateFromThread.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/shiftPlotAction.png
+doc/source/sample_code/img/simplewidget.png
+doc/source/sample_code/img/stackView.png
+doc/source/sample_code/img/syncaxis.png
+doc/source/sample_code/img/viewer3DVolume.png
examples/animatedicons.py
-examples/colorbar.py
+examples/customHdf5TreeModel.py
examples/fft.png
examples/fftPlotAction.py
examples/hdf5widget.py
examples/icons.py
examples/imageview.py
examples/periodicTable.py
+examples/plotContextMenu.py
+examples/plotItemsSelector.py
+examples/plotLimits.py
+examples/plotUpdateFromThread.py
+examples/plotWidget.py
+examples/printPreview.py
examples/scatterMask.py
examples/shiftPlotAction.py
examples/simplewidget.py
-examples/spectoh5.py
examples/stackView.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/debian9/changelog
+package/debian9/clean
+package/debian9/compat
+package/debian9/control
+package/debian9/gbp.conf
+package/debian9/python-silx-doc.doc-base
+package/debian9/rules
+package/debian9/watch
+package/debian9/source/format
+package/debian9/source/options
+package/desktop/org.silx.SilxView.desktop
+package/desktop/silx.png
+package/desktop/silx.svg
qtdesigner_plugins/README.rst
qtdesigner_plugins/plot1dplugin.py
qtdesigner_plugins/plot2dplugin.py
@@ -200,9 +285,13 @@ silx.egg-info/not-zip-safe
silx.egg-info/requires.txt
silx.egg-info/top_level.txt
silx/app/__init__.py
+silx/app/convert.py
+silx/app/qtutils.py
silx/app/setup.py
+silx/app/test_.py
silx/app/view.py
silx/app/test/__init__.py
+silx/app/test/test_convert.py
silx/app/test/test_view.py
silx/gui/__init__.py
silx/gui/_utils.py
@@ -211,6 +300,7 @@ silx/gui/icons.py
silx/gui/setup.py
silx/gui/_glutils/Context.py
silx/gui/_glutils/FramebufferTexture.py
+silx/gui/_glutils/OpenGLWidget.py
silx/gui/_glutils/Program.py
silx/gui/_glutils/Texture.py
silx/gui/_glutils/VertexBuffer.py
@@ -225,6 +315,7 @@ silx/gui/data/DataViewerFrame.py
silx/gui/data/DataViewerSelector.py
silx/gui/data/DataViews.py
silx/gui/data/Hdf5TableView.py
+silx/gui/data/HexaTableView.py
silx/gui/data/NXdataWidgets.py
silx/gui/data/NumpyAxesSelector.py
silx/gui/data/RecordTableView.py
@@ -247,6 +338,7 @@ silx/gui/fit/test/__init__.py
silx/gui/fit/test/testBackgroundWidget.py
silx/gui/fit/test/testFitConfig.py
silx/gui/fit/test/testFitWidget.py
+silx/gui/hdf5/Hdf5Formatter.py
silx/gui/hdf5/Hdf5HeaderView.py
silx/gui/hdf5/Hdf5Item.py
silx/gui/hdf5/Hdf5LoadingItem.py
@@ -258,19 +350,20 @@ silx/gui/hdf5/__init__.py
silx/gui/hdf5/_utils.py
silx/gui/hdf5/setup.py
silx/gui/hdf5/test/__init__.py
-silx/gui/hdf5/test/_mock.py
silx/gui/hdf5/test/test_hdf5.py
silx/gui/plot/AlphaSlider.py
silx/gui/plot/ColorBar.py
+silx/gui/plot/Colormap.py
silx/gui/plot/ColormapDialog.py
silx/gui/plot/Colors.py
+silx/gui/plot/ComplexImageView.py
silx/gui/plot/CurvesROIWidget.py
silx/gui/plot/ImageView.py
silx/gui/plot/Interaction.py
+silx/gui/plot/ItemsSelectionDialog.py
silx/gui/plot/LegendSelector.py
-silx/gui/plot/MPLColormap.py
+silx/gui/plot/LimitsHistory.py
silx/gui/plot/MaskToolsWidget.py
-silx/gui/plot/Plot.py
silx/gui/plot/PlotActions.py
silx/gui/plot/PlotEvents.py
silx/gui/plot/PlotInteraction.py
@@ -278,6 +371,7 @@ silx/gui/plot/PlotToolButtons.py
silx/gui/plot/PlotTools.py
silx/gui/plot/PlotWidget.py
silx/gui/plot/PlotWindow.py
+silx/gui/plot/PrintPreviewToolButton.py
silx/gui/plot/Profile.py
silx/gui/plot/ProfileMainWindow.py
silx/gui/plot/ScatterMaskToolsWidget.py
@@ -291,12 +385,18 @@ silx/gui/plot/_utils/setup.py
silx/gui/plot/_utils/ticklayout.py
silx/gui/plot/_utils/test/__init__.py
silx/gui/plot/_utils/test/test_ticklayout.py
+silx/gui/plot/actions/PlotAction.py
+silx/gui/plot/actions/__init__.py
+silx/gui/plot/actions/control.py
+silx/gui/plot/actions/fit.py
+silx/gui/plot/actions/histogram.py
+silx/gui/plot/actions/io.py
+silx/gui/plot/actions/medfilt.py
+silx/gui/plot/actions/mode.py
silx/gui/plot/backends/BackendBase.py
silx/gui/plot/backends/BackendMatplotlib.py
silx/gui/plot/backends/BackendOpenGL.py
-silx/gui/plot/backends/ModestImage.py
silx/gui/plot/backends/__init__.py
-silx/gui/plot/backends/_matplotlib.py
silx/gui/plot/backends/glutils/GLPlotCurve.py
silx/gui/plot/backends/glutils/GLPlotFrame.py
silx/gui/plot/backends/glutils/GLPlotImage.py
@@ -306,6 +406,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/axis.py
silx/gui/plot/items/core.py
silx/gui/plot/items/curve.py
silx/gui/plot/items/histogram.py
@@ -313,32 +414,44 @@ silx/gui/plot/items/image.py
silx/gui/plot/items/marker.py
silx/gui/plot/items/scatter.py
silx/gui/plot/items/shape.py
+silx/gui/plot/matplotlib/Colormap.py
+silx/gui/plot/matplotlib/ModestImage.py
+silx/gui/plot/matplotlib/__init__.py
silx/gui/plot/test/__init__.py
silx/gui/plot/test/testAlphaSlider.py
silx/gui/plot/test/testColorBar.py
+silx/gui/plot/test/testColormap.py
silx/gui/plot/test/testColormapDialog.py
silx/gui/plot/test/testColors.py
+silx/gui/plot/test/testComplexImageView.py
silx/gui/plot/test/testCurvesROIWidget.py
silx/gui/plot/test/testInteraction.py
+silx/gui/plot/test/testItem.py
silx/gui/plot/test/testLegendSelector.py
+silx/gui/plot/test/testLimitConstraints.py
silx/gui/plot/test/testMaskToolsWidget.py
-silx/gui/plot/test/testPlot.py
silx/gui/plot/test/testPlotInteraction.py
silx/gui/plot/test/testPlotTools.py
silx/gui/plot/test/testPlotWidget.py
+silx/gui/plot/test/testPlotWidgetNoBackend.py
silx/gui/plot/test/testPlotWindow.py
silx/gui/plot/test/testProfile.py
silx/gui/plot/test/testScatterMaskToolsWidget.py
silx/gui/plot/test/testStackView.py
-silx/gui/plot3d/Plot3DActions.py
-silx/gui/plot3d/Plot3DToolBar.py
+silx/gui/plot/test/testUtilsAxis.py
+silx/gui/plot/test/utils.py
+silx/gui/plot/utils/__init__.py
+silx/gui/plot/utils/axis.py
silx/gui/plot3d/Plot3DWidget.py
silx/gui/plot3d/Plot3DWindow.py
silx/gui/plot3d/SFViewParamTree.py
silx/gui/plot3d/ScalarFieldView.py
-silx/gui/plot3d/ViewpointToolBar.py
silx/gui/plot3d/__init__.py
silx/gui/plot3d/setup.py
+silx/gui/plot3d/actions/Plot3DAction.py
+silx/gui/plot3d/actions/__init__.py
+silx/gui/plot3d/actions/io.py
+silx/gui/plot3d/actions/mode.py
silx/gui/plot3d/scene/__init__.py
silx/gui/plot3d/scene/axes.py
silx/gui/plot3d/scene/camera.py
@@ -358,6 +471,11 @@ silx/gui/plot3d/scene/test/__init__.py
silx/gui/plot3d/scene/test/test_transform.py
silx/gui/plot3d/scene/test/test_utils.py
silx/gui/plot3d/test/__init__.py
+silx/gui/plot3d/test/testGL.py
+silx/gui/plot3d/test/testScalarFieldView.py
+silx/gui/plot3d/tools/ViewpointTools.py
+silx/gui/plot3d/tools/__init__.py
+silx/gui/plot3d/tools/toolbars.py
silx/gui/plot3d/utils/__init__.py
silx/gui/plot3d/utils/mng.py
silx/gui/qt/__init__.py
@@ -372,10 +490,13 @@ silx/gui/test/test_icons.py
silx/gui/test/test_qt.py
silx/gui/test/test_utils.py
silx/gui/test/utils.py
+silx/gui/widgets/FloatEdit.py
silx/gui/widgets/FrameBrowser.py
silx/gui/widgets/HierarchicalTableView.py
silx/gui/widgets/MedianFilterDialog.py
silx/gui/widgets/PeriodicTable.py
+silx/gui/widgets/PrintGeometryDialog.py
+silx/gui/widgets/PrintPreview.py
silx/gui/widgets/TableWidget.py
silx/gui/widgets/ThreadPoolPushButton.py
silx/gui/widgets/WaitingPushButton.py
@@ -384,34 +505,44 @@ silx/gui/widgets/setup.py
silx/gui/widgets/test/__init__.py
silx/gui/widgets/test/test_hierarchicaltableview.py
silx/gui/widgets/test/test_periodictable.py
+silx/gui/widgets/test/test_printpreview.py
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/test/__init__.py
silx/image/test/test_bilinear.py
silx/image/test/test_medianfilter.py
silx/image/test/test_shapes.py
+silx/image/test/test_tomography.py
silx/io/__init__.py
+silx/io/commonh5.py
silx/io/configdict.py
+silx/io/convert.py
silx/io/dictdump.py
silx/io/fabioh5.py
silx/io/nxdata.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
silx/io/spech5.py
silx/io/spectoh5.py
silx/io/utils.py
-silx/io/specfile/specfile.c
-silx/io/specfile/specfile.pyx
-silx/io/specfile/specfile_wrapper.pxd
silx/io/specfile/include/Lists.h
silx/io/specfile/include/SpecFile.h
silx/io/specfile/include/SpecFileCython.h
@@ -428,10 +559,12 @@ silx/io/specfile/src/sfmca.c
silx/io/specfile/src/sftools.c
silx/io/specfile/src/sfwrite.c
silx/io/test/__init__.py
+silx/io/test/test_commonh5.py
silx/io/test/test_dictdump.py
silx/io/test/test_fabioh5.py
silx/io/test/test_nxdata.py
silx/io/test/test_octaveh5.py
+silx/io/test/test_rawh5.py
silx/io/test/test_specfile.py
silx/io/test/test_specfilewrapper.py
silx/io/test/test_spech5.py
@@ -439,35 +572,42 @@ silx/io/test/test_spectoh5.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/combo.c
+silx/math/combo.pyx
silx/math/histogram.py
+silx/math/histogramnd_c.pxd
+silx/math/marchingcubes.cpp
+silx/math/marchingcubes.pyx
+silx/math/mc.pxd
silx/math/setup.py
-silx/math/combo/combo.c
-silx/math/combo/combo.pyx
-silx/math/combo/isnan.h
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
-silx/math/fit/filters/filters.c
-silx/math/fit/filters/filters.pyx
-silx/math/fit/filters/filters_wrapper.pxd
silx/math/fit/filters/include/filters.h
silx/math/fit/filters/src/smoothnd.c
silx/math/fit/filters/src/snip1d.c
silx/math/fit/filters/src/snip2d.c
silx/math/fit/filters/src/snip3d.c
silx/math/fit/filters/src/strip.c
-silx/math/fit/functions/functions.c
-silx/math/fit/functions/functions.pyx
-silx/math/fit/functions/functions_wrapper.pxd
silx/math/fit/functions/include/functions.h
silx/math/fit/functions/src/funs.c
-silx/math/fit/peaks/peaks.c
-silx/math/fit/peaks/peaks.pyx
-silx/math/fit/peaks/peaks_wrapper.pxd
silx/math/fit/peaks/include/peaks.h
silx/math/fit/peaks/src/peaks.c
silx/math/fit/test/__init__.py
@@ -477,20 +617,13 @@ silx/math/fit/test/test_fit.py
silx/math/fit/test/test_fitmanager.py
silx/math/fit/test/test_functions.py
silx/math/fit/test/test_peaks.py
-silx/math/histogramnd/chistogramnd.c
-silx/math/histogramnd/chistogramnd.pyx
-silx/math/histogramnd/chistogramnd_lut.c
-silx/math/histogramnd/chistogramnd_lut.pyx
-silx/math/histogramnd/histogramnd_c.pxd
silx/math/histogramnd/include/histogramnd_c.h
silx/math/histogramnd/include/templates.h
silx/math/histogramnd/include/msvc/stdint.h
silx/math/histogramnd/src/histogramnd_c.c
silx/math/histogramnd/src/histogramnd_template.c
-silx/math/marchingcubes/marchingcubes.cpp
-silx/math/marchingcubes/marchingcubes.pyx
+silx/math/include/isnan.h
silx/math/marchingcubes/mc.hpp
-silx/math/marchingcubes/mc.pxd
silx/math/marchingcubes/mc_lut.cpp
silx/math/medianfilter/__init__.py
silx/math/medianfilter/median_filter.pxd
@@ -511,9 +644,13 @@ silx/math/test/test_histogramnd_nominal.py
silx/math/test/test_histogramnd_vs_np.py
silx/math/test/test_marchingcubes.py
silx/opencl/__init__.py
+silx/opencl/backprojection.py
silx/opencl/common.py
+silx/opencl/linalg.py
silx/opencl/medfilt.py
silx/opencl/processing.py
+silx/opencl/projection.py
+silx/opencl/reconstruction.py
silx/opencl/setup.py
silx/opencl/utils.py
silx/opencl/sift/__init__.py
@@ -537,27 +674,40 @@ silx/opencl/sift/test/test_matching.py
silx/opencl/sift/test/test_preproc.py
silx/opencl/sift/test/test_reductions.py
silx/opencl/sift/test/test_transform.py
-silx/opencl/sift/test/utilstest.py
silx/opencl/test/__init__.py
silx/opencl/test/test_addition.py
+silx/opencl/test/test_array_utils.py
+silx/opencl/test/test_backprojection.py
+silx/opencl/test/test_linalg.py
silx/opencl/test/test_medfilt.py
+silx/opencl/test/test_projection.py
silx/resources/__init__.py
+silx/resources/gui/colormaps/inferno.npy
+silx/resources/gui/colormaps/magma.npy
+silx/resources/gui/colormaps/plasma.npy
+silx/resources/gui/colormaps/viridis.npy
silx/resources/gui/icons/3d-plane-normal-x.png
silx/resources/gui/icons/3d-plane-normal-x.svg
silx/resources/gui/icons/3d-plane-normal-y.png
silx/resources/gui/icons/3d-plane-normal-y.svg
silx/resources/gui/icons/3d-plane-normal-z.png
silx/resources/gui/icons/3d-plane-normal-z.svg
+silx/resources/gui/icons/3d-plane-pan.png
+silx/resources/gui/icons/3d-plane-pan.svg
silx/resources/gui/icons/3d-plane.png
silx/resources/gui/icons/3d-plane.svg
silx/resources/gui/icons/arrow-keys.png
silx/resources/gui/icons/arrow-keys.svg
+silx/resources/gui/icons/axis.png
+silx/resources/gui/icons/axis.svg
silx/resources/gui/icons/camera.png
silx/resources/gui/icons/camera.svg
silx/resources/gui/icons/clipboard.png
silx/resources/gui/icons/clipboard.svg
silx/resources/gui/icons/close.png
silx/resources/gui/icons/close.svg
+silx/resources/gui/icons/colorbar.png
+silx/resources/gui/icons/colorbar.svg
silx/resources/gui/icons/colormap.png
silx/resources/gui/icons/colormap.svg
silx/resources/gui/icons/crop.png
@@ -620,10 +770,14 @@ silx/resources/gui/icons/item-3dim.png
silx/resources/gui/icons/item-3dim.svg
silx/resources/gui/icons/item-ndim.png
silx/resources/gui/icons/item-ndim.svg
+silx/resources/gui/icons/item-none.png
+silx/resources/gui/icons/item-none.svg
silx/resources/gui/icons/item-object.png
silx/resources/gui/icons/item-object.svg
silx/resources/gui/icons/last.png
silx/resources/gui/icons/last.svg
+silx/resources/gui/icons/math-amplitude.png
+silx/resources/gui/icons/math-amplitude.svg
silx/resources/gui/icons/math-average.png
silx/resources/gui/icons/math-average.svg
silx/resources/gui/icons/math-derive.png
@@ -632,6 +786,8 @@ silx/resources/gui/icons/math-energy.png
silx/resources/gui/icons/math-energy.svg
silx/resources/gui/icons/math-fit.png
silx/resources/gui/icons/math-fit.svg
+silx/resources/gui/icons/math-imaginary.png
+silx/resources/gui/icons/math-imaginary.svg
silx/resources/gui/icons/math-normalize.png
silx/resources/gui/icons/math-normalize.svg
silx/resources/gui/icons/math-peak-reset.png
@@ -640,6 +796,14 @@ silx/resources/gui/icons/math-peak-search.png
silx/resources/gui/icons/math-peak-search.svg
silx/resources/gui/icons/math-peak.png
silx/resources/gui/icons/math-peak.svg
+silx/resources/gui/icons/math-phase-color-log.png
+silx/resources/gui/icons/math-phase-color-log.svg
+silx/resources/gui/icons/math-phase-color.png
+silx/resources/gui/icons/math-phase-color.svg
+silx/resources/gui/icons/math-phase.png
+silx/resources/gui/icons/math-phase.svg
+silx/resources/gui/icons/math-real.png
+silx/resources/gui/icons/math-real.svg
silx/resources/gui/icons/math-sigma.png
silx/resources/gui/icons/math-sigma.svg
silx/resources/gui/icons/math-smooth.png
@@ -656,6 +820,8 @@ silx/resources/gui/icons/next.png
silx/resources/gui/icons/next.svg
silx/resources/gui/icons/normal.png
silx/resources/gui/icons/normal.svg
+silx/resources/gui/icons/pan.png
+silx/resources/gui/icons/pan.svg
silx/resources/gui/icons/pixel-intensities.png
silx/resources/gui/icons/pixel-intensities.svg
silx/resources/gui/icons/plot-grid.png
@@ -701,6 +867,8 @@ silx/resources/gui/icons/profile2D.png
silx/resources/gui/icons/profile2D.svg
silx/resources/gui/icons/remove.png
silx/resources/gui/icons/remove.svg
+silx/resources/gui/icons/rotate-3d.png
+silx/resources/gui/icons/rotate-3d.svg
silx/resources/gui/icons/rudder.png
silx/resources/gui/icons/rudder.svg
silx/resources/gui/icons/selected.png
@@ -733,8 +901,6 @@ silx/resources/gui/icons/sliders-on.png
silx/resources/gui/icons/sliders-on.svg
silx/resources/gui/icons/spec.png
silx/resources/gui/icons/spec.svg
-silx/resources/gui/icons/test-png.png
-silx/resources/gui/icons/test-svg.svg
silx/resources/gui/icons/view-1d.png
silx/resources/gui/icons/view-1d.svg
silx/resources/gui/icons/view-2d-stack.png
@@ -759,6 +925,8 @@ silx/resources/gui/icons/view-text.png
silx/resources/gui/icons/view-text.svg
silx/resources/gui/icons/window-new.png
silx/resources/gui/icons/window-new.svg
+silx/resources/gui/icons/zoom-back.png
+silx/resources/gui/icons/zoom-back.svg
silx/resources/gui/icons/zoom-in.png
silx/resources/gui/icons/zoom-in.svg
silx/resources/gui/icons/zoom-original.png
@@ -767,41 +935,48 @@ silx/resources/gui/icons/zoom-out.png
silx/resources/gui/icons/zoom-out.svg
silx/resources/gui/icons/zoom.png
silx/resources/gui/icons/zoom.svg
-silx/resources/gui/icons/animated/process-working-00.png
-silx/resources/gui/icons/animated/process-working-01.png
-silx/resources/gui/icons/animated/process-working-02.png
-silx/resources/gui/icons/animated/process-working-03.png
-silx/resources/gui/icons/animated/process-working-04.png
-silx/resources/gui/icons/animated/process-working-05.png
-silx/resources/gui/icons/animated/process-working-06.png
-silx/resources/gui/icons/animated/process-working-07.png
-silx/resources/gui/icons/animated/process-working-08.png
-silx/resources/gui/icons/animated/process-working-09.png
-silx/resources/gui/icons/animated/process-working-10.png
-silx/resources/gui/icons/animated/process-working-11.png
-silx/resources/gui/icons/animated/process-working-12.png
-silx/resources/gui/icons/animated/process-working-13.png
-silx/resources/gui/icons/animated/process-working-14.png
-silx/resources/gui/icons/animated/process-working-15.png
-silx/resources/gui/icons/animated/process-working-16.png
-silx/resources/gui/icons/animated/process-working-17.png
-silx/resources/gui/icons/animated/process-working-18.png
-silx/resources/gui/icons/animated/process-working-19.png
-silx/resources/gui/icons/animated/process-working-20.png
-silx/resources/gui/icons/animated/process-working-21.png
-silx/resources/gui/icons/animated/process-working-22.png
-silx/resources/gui/icons/animated/process-working-23.png
-silx/resources/gui/icons/animated/process-working-24.png
-silx/resources/gui/icons/animated/process-working-25.png
-silx/resources/gui/icons/animated/process-working-26.png
-silx/resources/gui/icons/animated/process-working-27.png
-silx/resources/gui/icons/animated/process-working-28.png
-silx/resources/gui/icons/animated/process-working-29.png
-silx/resources/gui/icons/animated/process-working-30.png
+silx/resources/gui/icons/process-working/00.png
+silx/resources/gui/icons/process-working/01.png
+silx/resources/gui/icons/process-working/02.png
+silx/resources/gui/icons/process-working/03.png
+silx/resources/gui/icons/process-working/04.png
+silx/resources/gui/icons/process-working/05.png
+silx/resources/gui/icons/process-working/06.png
+silx/resources/gui/icons/process-working/07.png
+silx/resources/gui/icons/process-working/08.png
+silx/resources/gui/icons/process-working/09.png
+silx/resources/gui/icons/process-working/10.png
+silx/resources/gui/icons/process-working/11.png
+silx/resources/gui/icons/process-working/12.png
+silx/resources/gui/icons/process-working/13.png
+silx/resources/gui/icons/process-working/14.png
+silx/resources/gui/icons/process-working/15.png
+silx/resources/gui/icons/process-working/16.png
+silx/resources/gui/icons/process-working/17.png
+silx/resources/gui/icons/process-working/18.png
+silx/resources/gui/icons/process-working/19.png
+silx/resources/gui/icons/process-working/20.png
+silx/resources/gui/icons/process-working/21.png
+silx/resources/gui/icons/process-working/22.png
+silx/resources/gui/icons/process-working/23.png
+silx/resources/gui/icons/process-working/24.png
+silx/resources/gui/icons/process-working/25.png
+silx/resources/gui/icons/process-working/26.png
+silx/resources/gui/icons/process-working/27.png
+silx/resources/gui/icons/process-working/28.png
+silx/resources/gui/icons/process-working/29.png
+silx/resources/gui/icons/process-working/30.png
+silx/resources/gui/logo/silx.png
+silx/resources/gui/logo/silx.svg
silx/resources/opencl/addition.cl
+silx/resources/opencl/array_utils.cl
+silx/resources/opencl/backproj.cl
+silx/resources/opencl/backproj_helper.cl
silx/resources/opencl/bitonic.cl
+silx/resources/opencl/linalg.cl
silx/resources/opencl/medfilt.cl
silx/resources/opencl/preprocess.cl
+silx/resources/opencl/proj.cl
silx/resources/opencl/sift/addition.cl
silx/resources/opencl/sift/algebra.cl
silx/resources/opencl/sift/convolution.cl
@@ -829,20 +1004,25 @@ silx/test/utils.py
silx/third_party/EdfFile.py
silx/third_party/TiffIO.py
silx/third_party/__init__.py
+silx/third_party/enum.py
silx/third_party/setup.py
silx/third_party/six.py
silx/third_party/_local/__init__.py
+silx/third_party/_local/enum.py
silx/third_party/_local/six.py
silx/utils/__init__.py
silx/utils/array_like.py
-silx/utils/decorators.py
+silx/utils/deprecation.py
silx/utils/html.py
silx/utils/launcher.py
+silx/utils/proxy.py
silx/utils/setup.py
silx/utils/weakref.py
silx/utils/test/__init__.py
silx/utils/test/test_array_like.py
+silx/utils/test/test_deprecation.py
silx/utils/test/test_html.py
silx/utils/test/test_launcher.py
silx/utils/test/test_launcher_command.py
+silx/utils/test/test_proxy.py
silx/utils/test/test_weakref.py \ No newline at end of file
diff --git a/silx/__init__.py b/silx/__init__.py
index cfb306b..8dab7e1 100644
--- a/silx/__init__.py
+++ b/silx/__init__.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2015-2016 European Synchrotron Radiation Facility
+# Copyright (c) 2015-2017 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,7 +32,10 @@ __date__ = "23/05/2016"
import os as _os
import logging as _logging
-_logging.basicConfig() # Make sure logging is initialised
+
+# Attach a do nothing logging handler for silx
+_logging.getLogger(__name__).addHandler(_logging.NullHandler())
+
project = _os.path.basename(_os.path.dirname(_os.path.abspath(__file__)))
diff --git a/silx/__main__.py b/silx/__main__.py
index 4c05ef3..8323b03 100644
--- a/silx/__main__.py
+++ b/silx/__main__.py
@@ -2,7 +2,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2004-2016 European Synchrotron Radiation Facility
+# Copyright (c) 2017 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,9 +30,9 @@ Your environment should provide a command `silx`. You can reach help with
`silx --help`, and check the version with `silx --version`.
"""
-__authors__ = ["V. Valls"]
+__authors__ = ["V. Valls", "P. Knobel"]
__license__ = "MIT"
-__date__ = "18/04/2017"
+__date__ = "29/06/2017"
import logging
@@ -46,8 +46,8 @@ import silx._version
def main():
"""Main function of the launcher
- This function is referenced in the setup.py file.
- Thisfor it is executed by a launcher script generated by setuptools.
+ This function is referenced in the setup.py file, to create a
+ launcher script generated by setuptools.
:rtype: int
:returns: The execution status
@@ -56,6 +56,12 @@ def main():
launcher.add_command("view",
module_name="silx.app.view",
description="Browse a data file with a GUI")
+ launcher.add_command("convert",
+ module_name="silx.app.convert",
+ description="Convert and concatenate files into a HDF5 file")
+ launcher.add_command("test",
+ module_name="silx.app.test_",
+ description="Launch silx unittest")
status = launcher.execute(sys.argv)
return status
diff --git a/silx/app/convert.py b/silx/app/convert.py
new file mode 100644
index 0000000..a092ec1
--- /dev/null
+++ b/silx/app/convert.py
@@ -0,0 +1,283 @@
+# coding: utf-8
+# /*##########################################################################
+# Copyright (C) 2017 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.
+#
+# ############################################################################*/
+"""Convert silx supported data files into HDF5 files"""
+
+import ast
+import sys
+import os
+import argparse
+from glob import glob
+import logging
+import numpy
+import silx
+
+
+__authors__ = ["P. Knobel"]
+__license__ = "MIT"
+__date__ = "12/09/2017"
+
+
+_logger = logging.getLogger(__name__)
+"""Module logger"""
+
+
+def main(argv):
+ """
+ Main function to launch the converter as an application
+
+ :param argv: Command line arguments
+ :returns: exit status
+ """
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument(
+ 'input_files',
+ nargs="+",
+ help='Input files (EDF, SPEC)')
+ parser.add_argument(
+ '-o', '--output-uri',
+ nargs="?",
+ help='Output file (HDF5). If omitted, it will be the '
+ 'concatenated input file names, with a ".h5" suffix added.'
+ ' An URI can be provided to write the data into a specific '
+ 'group in the output file: /path/to/file::/path/to/group')
+ parser.add_argument(
+ '-m', '--mode',
+ default="w-",
+ help='Write mode: "r+" (read/write, file must exist), '
+ '"w" (write, existing file is lost), '
+ '"w-" (write, fail if file exists) or '
+ '"a" (read/write if exists, create otherwise)')
+ parser.add_argument(
+ '--no-root-group',
+ action="store_true",
+ help='This option disables the default behavior of creating a '
+ 'root group (entry) for each file to be converted. When '
+ 'merging multiple input files, this can cause conflicts '
+ 'when datasets have the same name (see --overwrite-data).')
+ parser.add_argument(
+ '--overwrite-data',
+ action="store_true",
+ help='If the output path exists and an input dataset has the same'
+ ' name as an existing output dataset, overwrite the output '
+ 'dataset (in modes "r+" or "a").')
+ parser.add_argument(
+ '--min-size',
+ type=int,
+ default=500,
+ help='Minimum number of elements required to be in a dataset to '
+ 'apply compression or chunking (default 500).')
+ parser.add_argument(
+ '--chunks',
+ nargs="?",
+ const="auto",
+ help='Chunk shape. Provide an argument that evaluates as a python '
+ 'tuple (e.g. "(1024, 768)"). If this option is provided without '
+ 'specifying an argument, the h5py library will guess a chunk for '
+ 'you. Note that if you specify an explicit chunking shape, it '
+ 'will be applied identically to all datasets with a large enough '
+ 'size (see --min-size). ')
+ parser.add_argument(
+ '--compression',
+ nargs="?",
+ const="gzip",
+ help='Compression filter. By default, the datasets in the output '
+ 'file are not compressed. If this option is specified without '
+ 'argument, the GZIP compression is used. Additional compression '
+ 'filters may be available, depending on your HDF5 installation.')
+
+ def check_gzip_compression_opts(value):
+ ivalue = int(value)
+ if ivalue < 0 or ivalue > 9:
+ raise argparse.ArgumentTypeError(
+ "--compression-opts must be an int from 0 to 9")
+ return ivalue
+
+ parser.add_argument(
+ '--compression-opts',
+ type=check_gzip_compression_opts,
+ help='Compression options. For "gzip", this may be an integer from '
+ '0 to 9, with a default of 4. This is only supported for GZIP.')
+ parser.add_argument(
+ '--shuffle',
+ action="store_true",
+ help='Enables the byte shuffle filter, may improve the compression '
+ 'ratio for block oriented compressors like GZIP or LZF.')
+ parser.add_argument(
+ '--fletcher32',
+ action="store_true",
+ help='Adds a checksum to each chunk to detect data corruption.')
+ parser.add_argument(
+ '--debug',
+ action="store_true",
+ default=False,
+ help='Set logging system in debug mode')
+
+ options = parser.parse_args(argv[1:])
+
+ # some shells (windows) don't interpret wildcard characters (*, ?, [])
+ old_input_list = list(options.input_files)
+ options.input_files = []
+ for fname in old_input_list:
+ globbed_files = glob(fname)
+ if not globbed_files:
+ # no files found, keep the name as it is, to raise an error later
+ options.input_files += [fname]
+ else:
+ options.input_files += globbed_files
+ old_input_list = None
+
+ if options.debug:
+ logging.root.setLevel(logging.DEBUG)
+
+ # Import most of the things here to be sure to use the right logging level
+ try:
+ # it should be loaded before h5py
+ import hdf5plugin # noqa
+ except ImportError:
+ _logger.debug("Backtrace", exc_info=True)
+ hdf5plugin = None
+
+ try:
+ import h5py
+ from silx.io.convert import write_to_h5
+ except ImportError:
+ _logger.debug("Backtrace", exc_info=True)
+ h5py = None
+ write_to_h5 = None
+
+ if h5py is None:
+ message = "Module 'h5py' is not installed but is mandatory."\
+ + " You can install it using \"pip install h5py\"."
+ _logger.error(message)
+ return -1
+
+ if hdf5plugin is None:
+ message = "Module 'hdf5plugin' is not installed. It supports additional hdf5"\
+ + " compressions. You can install it using \"pip install hdf5plugin\"."
+ _logger.debug(message)
+
+ # Test that the output path is writeable
+ if options.output_uri is None:
+ input_basenames = [os.path.basename(name) for name in options.input_files]
+ output_name = ''.join(input_basenames) + ".h5"
+ _logger.info("No output file specified, using %s", output_name)
+ hdf5_path = "/"
+ else:
+ if "::" in options.output_uri:
+ output_name, hdf5_path = options.output_uri.split("::")
+ else:
+ output_name, hdf5_path = options.output_uri, "/"
+
+ if os.path.isfile(output_name):
+ if options.mode == "w-":
+ _logger.error("Output file %s exists and mode is 'w-'"
+ " (write, file must not exist). Aborting.",
+ output_name)
+ return -1
+ elif not os.access(output_name, os.W_OK):
+ _logger.error("Output file %s exists and is not writeable.",
+ output_name)
+ return -1
+ elif options.mode == "w":
+ _logger.info("Output file %s exists and mode is 'w'. "
+ "Overwriting existing file.", output_name)
+ elif options.mode in ["a", "r+"]:
+ _logger.info("Appending data to existing file %s.",
+ output_name)
+ else:
+ if options.mode == "r+":
+ _logger.error("Output file %s does not exist and mode is 'r+'"
+ " (append, file must exist). Aborting.",
+ output_name)
+ return -1
+ else:
+ _logger.info("Creating new output file %s.",
+ output_name)
+
+ # Test that all input files exist and are readable
+ bad_input = False
+ for fname in options.input_files:
+ if not os.access(fname, os.R_OK):
+ _logger.error("Cannot read input file %s.",
+ fname)
+ bad_input = True
+ if bad_input:
+ _logger.error("Aborting.")
+ return -1
+
+ # create_dataset special args
+ create_dataset_args = {}
+ if options.chunks is not None:
+ if options.chunks.lower() in ["auto", "true"]:
+ create_dataset_args["chunks"] = True
+ else:
+ try:
+ chunks = ast.literal_eval(options.chunks)
+ except (ValueError, SyntaxError):
+ _logger.error("Invalid --chunks argument %s", options.chunks)
+ return -1
+ if not isinstance(chunks, (tuple, list)):
+ _logger.error("--chunks argument str does not evaluate to a tuple")
+ return -1
+ else:
+ nitems = numpy.prod(chunks)
+ nbytes = nitems * 8
+ if nbytes > 10**6:
+ _logger.warning("Requested chunk size might be larger than"
+ " the default 1MB chunk cache, for float64"
+ " data. This can dramatically affect I/O "
+ "performances.")
+ create_dataset_args["chunks"] = chunks
+
+ if options.compression is not None:
+ create_dataset_args["compression"] = options.compression
+
+ if options.compression_opts is not None:
+ create_dataset_args["compression_opts"] = options.compression_opts
+
+ if options.shuffle:
+ create_dataset_args["shuffle"] = True
+
+ if options.fletcher32:
+ create_dataset_args["fletcher32"] = True
+
+ with h5py.File(output_name, mode=options.mode) as h5f:
+ for input_name in options.input_files:
+ hdf5_path_for_file = hdf5_path
+ if not options.no_root_group:
+ hdf5_path_for_file = hdf5_path.rstrip("/") + "/" + os.path.basename(input_name)
+ write_to_h5(input_name, h5f,
+ h5path=hdf5_path_for_file,
+ overwrite_data=options.overwrite_data,
+ create_dataset_args=create_dataset_args,
+ min_size=options.min_size)
+
+ # append the convert command to the creator attribute, for NeXus files
+ creator = h5f[hdf5_path_for_file].attrs.get("creator", b"").decode()
+ convert_command = " ".join(argv)
+ if convert_command not in creator:
+ h5f[hdf5_path_for_file].attrs["creator"] = \
+ numpy.string_(creator + "; convert command: %s" % " ".join(argv))
+
+ return 0
diff --git a/silx/app/qtutils.py b/silx/app/qtutils.py
new file mode 100644
index 0000000..4c29c84
--- /dev/null
+++ b/silx/app/qtutils.py
@@ -0,0 +1,243 @@
+# coding: utf-8
+# /*##########################################################################
+# Copyright (C) 2016-2017 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.
+#
+# ############################################################################*/
+"""Qt utils for Silx applications"""
+
+__authors__ = ["V. Valls"]
+__license__ = "MIT"
+__date__ = "22/09/2017"
+
+import sys
+
+try:
+ # it should be loaded before h5py
+ import hdf5plugin # noqa
+except ImportError:
+ hdf5plugin = None
+
+try:
+ import h5py
+except ImportError:
+ h5py = None
+
+try:
+ import fabio
+except ImportError:
+ fabio = None
+
+from silx.gui import qt
+from silx.gui import icons
+
+_LICENSE_TEMPLATE = """<p align="center">
+<b>Copyright (C) {year} European Synchrotron Radiation Facility</b>
+</p>
+
+<p align="justify">
+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:
+</p>
+
+<p align="justify">
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+</p>
+
+<p align="justify">
+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.
+</p>
+"""
+
+
+class About(qt.QDialog):
+ """
+ Util dialog to display an common about box for all the silx GUIs.
+ """
+
+ def __init__(self, parent=None):
+ """
+ :param files_: List of HDF5 or Spec files (pathes or
+ :class:`silx.io.spech5.SpecH5` or :class:`h5py.File`
+ instances)
+ """
+ super(About, self).__init__(parent)
+ self.__createLayout()
+ self.setSizePolicy(qt.QSizePolicy.Fixed, qt.QSizePolicy.Fixed)
+ self.setModal(True)
+ self.setApplicationName(None)
+
+ def __createLayout(self):
+ layout = qt.QVBoxLayout(self)
+ layout.setContentsMargins(24, 15, 24, 20)
+ layout.setSpacing(8)
+
+ self.__label = qt.QLabel(self)
+ self.__label.setWordWrap(True)
+ flags = self.__label.textInteractionFlags()
+ flags = flags | qt.Qt.TextSelectableByKeyboard
+ flags = flags | qt.Qt.TextSelectableByMouse
+ self.__label.setTextInteractionFlags(flags)
+ self.__label.setOpenExternalLinks(True)
+ self.__label.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Preferred)
+
+ licenseButton = qt.QPushButton(self)
+ licenseButton.setText("License...")
+ licenseButton.clicked.connect(self.__displayLicense)
+ licenseButton.setAutoDefault(False)
+
+ self.__options = qt.QDialogButtonBox()
+ self.__options.addButton(licenseButton, qt.QDialogButtonBox.ActionRole)
+ okButton = self.__options.addButton(qt.QDialogButtonBox.Ok)
+ okButton.setDefault(True)
+ okButton.clicked.connect(self.accept)
+
+ layout.addWidget(self.__label)
+ layout.addWidget(self.__options)
+ layout.setStretch(0, 100)
+ layout.setStretch(1, 0)
+
+ def getHtmlLicense(self):
+ """Returns the text license in HTML format.
+
+ :rtype: str
+ """
+ from silx._version import __date__ as date
+ year = date.split("/")[2]
+ info = dict(
+ year=year
+ )
+ textLicense = _LICENSE_TEMPLATE.format(**info)
+ return textLicense
+
+ def __displayLicense(self):
+ """Displays the license used by silx."""
+ text = self.getHtmlLicense()
+ licenseDialog = qt.QMessageBox(self)
+ licenseDialog.setWindowTitle("License")
+ licenseDialog.setText(text)
+ licenseDialog.exec_()
+
+ def setApplicationName(self, name):
+ self.__applicationName = name
+ if name is None:
+ self.setWindowTitle("About")
+ else:
+ self.setWindowTitle("About %s" % name)
+ self.__updateText()
+
+ @staticmethod
+ def __formatOptionalLibraries(name, isAvailable):
+ """Utils to format availability of features"""
+ if isAvailable:
+ template = '<b>%s</b> is <font color="green">installed</font>'
+ else:
+ template = '<b>%s</b> is <font color="red">not installed</font>'
+ return template % name
+
+ def __updateText(self):
+ """Update the content of the dialog according to the settings."""
+ import silx._version
+
+ message = """<table>
+ <tr><td width="50%" align="center" valign="middle">
+ <img src="{silx_image_path}" width="100" />
+ </td><td width="50%" align="center" valign="middle">
+ <b>{application_name}</b>
+ <br />
+ <br />{silx_version}
+ <br />
+ <br /><a href="{project_url}">Upstream project on GitHub</a>
+ </td></tr>
+ </table>
+ <dl>
+ <dt><b>Silx version</b></dt><dd>{silx_version}</dd>
+ <dt><b>Qt version</b></dt><dd>{qt_version}</dd>
+ <dt><b>Qt binding</b></dt><dd>{qt_binding}</dd>
+ <dt><b>Python version</b></dt><dd>{python_version}</dd>
+ <dt><b>Optional libraries</b></dt><dd>{optional_lib}</dd>
+ </dl>
+ <p>
+ Copyright (C) <a href="{esrf_url}">European Synchrotron Radiation Facility</a>
+ </p>
+ """
+ optional_lib = []
+ optional_lib.append(self.__formatOptionalLibraries("FabIO", fabio is not None))
+ optional_lib.append(self.__formatOptionalLibraries("H5py", h5py is not None))
+ optional_lib.append(self.__formatOptionalLibraries("hdf5plugin", hdf5plugin is not None))
+
+ # Access to the logo in SVG or PNG
+ logo = icons.getQFile("../logo/silx")
+
+ info = dict(
+ application_name=self.__applicationName,
+ esrf_url="http://www.esrf.eu",
+ project_url="https://github.com/silx-kit/silx",
+ silx_version=silx._version.version,
+ qt_binding=qt.BINDING,
+ qt_version=qt.qVersion(),
+ python_version=sys.version.replace("\n", "<br />"),
+ optional_lib="<br />".join(optional_lib),
+ silx_image_path=logo.fileName()
+ )
+
+ self.__label.setText(message.format(**info))
+ self.__updateSize()
+
+ def __updateSize(self):
+ """Force the size to a QMessageBox like size."""
+ screenSize = qt.QApplication.desktop().availableGeometry(qt.QCursor.pos()).size()
+ hardLimit = min(screenSize.width() - 480, 1000)
+ if screenSize.width() <= 1024:
+ hardLimit = screenSize.width()
+ softLimit = min(screenSize.width() / 2, 420)
+
+ layoutMinimumSize = self.layout().totalMinimumSize()
+ width = layoutMinimumSize.width()
+ if width > softLimit:
+ width = softLimit
+ if width > hardLimit:
+ width = hardLimit
+
+ height = layoutMinimumSize.height()
+ self.setFixedSize(width, height)
+
+ @staticmethod
+ def about(parent, applicationName):
+ """Displays a silx about box with title and text text.
+
+ :param qt.QWidget parent: The parent widget
+ :param str title: The title of the dialog
+ :param str applicationName: The content of the dialog
+ """
+ dialog = About(parent)
+ dialog.setApplicationName(applicationName)
+ dialog.exec_()
diff --git a/silx/app/test/__init__.py b/silx/app/test/__init__.py
index 54241dc..0c22386 100644
--- a/silx/app/test/__init__.py
+++ b/silx/app/test/__init__.py
@@ -26,16 +26,14 @@ __authors__ = ["V. Valls"]
__license__ = "MIT"
__date__ = "30/03/2017"
-
-import logging
-import os
-import sys
import unittest
-
-_logger = logging.getLogger(__name__)
+from . import test_view
+from . import test_convert
def suite():
test_suite = unittest.TestSuite()
+ test_suite.addTest(test_view.suite())
+ test_suite.addTest(test_convert.suite())
return test_suite
diff --git a/silx/app/test/test_convert.py b/silx/app/test/test_convert.py
new file mode 100644
index 0000000..3215460
--- /dev/null
+++ b/silx/app/test/test_convert.py
@@ -0,0 +1,182 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2016-2017 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.
+#
+# ###########################################################################*/
+"""Module testing silx.app.convert"""
+
+__authors__ = ["P. Knobel"]
+__license__ = "MIT"
+__date__ = "12/09/2017"
+
+
+import os
+import sys
+import tempfile
+import unittest
+import io
+import gc
+
+try:
+ import h5py
+except ImportError:
+ h5py = None
+
+import silx
+from .. import convert
+from silx.test import utils
+
+
+
+# content of a spec file
+sftext = """#F /tmp/sf.dat
+#E 1455180875
+#D Thu Feb 11 09:54:35 2016
+#C imaging User = opid17
+#O0 Pslit HGap MRTSlit UP MRTSlit DOWN
+#O1 Sslit1 VOff Sslit1 HOff Sslit1 VGap
+#o0 pshg mrtu mrtd
+#o2 ss1vo ss1ho ss1vg
+
+#J0 Seconds IA ion.mono Current
+#J1 xbpmc2 idgap1 Inorm
+
+#S 1 ascan ss1vo -4.55687 -0.556875 40 0.2
+#D Thu Feb 11 09:55:20 2016
+#T 0.2 (Seconds)
+#P0 180.005 -0.66875 0.87125
+#P1 14.74255 16.197579 12.238283
+#N 4
+#L MRTSlit UP second column 3rd_col
+-1.23 5.89 8
+8.478100E+01 5 1.56
+3.14 2.73 -3.14
+1.2 2.3 3.4
+
+#S 1 aaaaaa
+#D Thu Feb 11 10:00:32 2016
+#@MCADEV 1
+#@MCA %16C
+#@CHANN 3 0 2 1
+#@CALIB 1 2 3
+#N 3
+#L uno duo
+1 2
+@A 0 1 2
+@A 10 9 8
+3 4
+@A 3.1 4 5
+@A 7 6 5
+5 6
+@A 6 7.7 8
+@A 4 3 2
+"""
+
+
+class TestConvertCommand(unittest.TestCase):
+ """Test command line parsing"""
+
+ def testHelp(self):
+ # option -h must cause a `raise SystemExit` or a `return 0`
+ try:
+ result = convert.main(["convert", "--help"])
+ except SystemExit as e:
+ result = e.args[0]
+ self.assertEqual(result, 0)
+
+ @unittest.skipUnless(h5py is None,
+ "h5py is installed, this test is specific to h5py missing")
+ @utils.test_logging(convert._logger.name, error=1)
+ def testH5pyNotInstalled(self):
+ result = convert.main(["convert", "foo.spec", "bar.edf"])
+ # we explicitly return -1 if h5py is not imported
+ self.assertNotEqual(result, 0)
+
+ @unittest.skipIf(h5py is None, "h5py is required to test convert")
+ def testWrongOption(self):
+ # presence of a wrong option must cause a SystemExit or a return
+ # with a non-zero status
+ try:
+ result = convert.main(["convert", "--foo"])
+ except SystemExit as e:
+ result = e.args[0]
+ self.assertNotEqual(result, 0)
+
+ @unittest.skipIf(h5py is None, "h5py is required to test convert")
+ @utils.test_logging(convert._logger.name, error=3)
+ # one error log per missing file + one "Aborted" error log
+ def testWrongFiles(self):
+ result = convert.main(["convert", "foo.spec", "bar.edf"])
+ self.assertNotEqual(result, 0)
+
+ @unittest.skipIf(h5py is None, "h5py is required to test convert")
+ def testFile(self):
+ # create a writable temp directory
+ tempdir = tempfile.mkdtemp()
+
+ # write a temporary SPEC file
+ specname = os.path.join(tempdir, "input.dat")
+ with io.open(specname, "wb") as fd:
+ if sys.version < '3.0':
+ fd.write(sftext)
+ else:
+ fd.write(bytes(sftext, 'ascii'))
+
+ # convert it
+ h5name = os.path.join(tempdir, "output.h5")
+ command_list = ["convert", "-m", "w",
+ "--no-root-group", specname, "-o", h5name]
+ result = convert.main(command_list)
+
+ self.assertEqual(result, 0)
+ self.assertTrue(os.path.isfile(h5name))
+
+ with h5py.File(h5name, "r") as h5f:
+ title12 = h5f["/1.2/title"][()]
+ if sys.version > '3.0':
+ title12 = title12.decode()
+ self.assertEqual(title12,
+ "1 aaaaaa")
+
+ creator = h5f.attrs.get("creator")
+ self.assertIsNotNone(creator, "No creator attribute in NXroot group")
+ creator = creator.decode() # make sure we can compare creator with native string
+ self.assertTrue(creator.startswith("silx %s" % silx.version))
+ command = " ".join(command_list)
+ self.assertTrue(creator.endswith(command))
+
+ # delete input file
+ gc.collect() # necessary to free spec file on Windows
+ os.unlink(specname)
+ os.unlink(h5name)
+ os.rmdir(tempdir)
+
+
+def suite():
+ test_suite = unittest.TestSuite()
+ loader = unittest.defaultTestLoader.loadTestsFromTestCase
+ test_suite.addTest(loader(TestConvertCommand))
+ return test_suite
+
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='suite')
diff --git a/silx/app/test/test_view.py b/silx/app/test/test_view.py
index 774bc01..e55e4f3 100644
--- a/silx/app/test/test_view.py
+++ b/silx/app/test/test_view.py
@@ -26,13 +26,29 @@
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "12/04/2017"
+__date__ = "29/09/2017"
import unittest
-from silx.gui.test.utils import TestCaseQt
-from .. import view
import sys
+import os
+
+
+# TODO: factor this code with silx.gui.test
+with_qt = False
+if sys.platform.startswith('linux') and not os.environ.get('DISPLAY', ''):
+ reason = 'test disabled (DISPLAY env. variable not set)'
+ view = None
+ TestCaseQt = unittest.TestCase
+elif os.environ.get('WITH_QT_TEST', 'True') == 'False':
+ reason = "test disabled (env. variable WITH_QT_TEST=False)"
+ view = None
+ TestCaseQt = unittest.TestCase
+else:
+ from silx.gui.test.utils import TestCaseQt
+ from .. import view
+ with_qt = True
+ reason = ""
class QApplicationMock(object):
@@ -64,6 +80,7 @@ class ViewerMock(object):
pass
+@unittest.skipUnless(with_qt, "Qt binding required for TestLauncher")
class TestLauncher(unittest.TestCase):
"""Test command line parsing"""
@@ -83,9 +100,9 @@ class TestLauncher(unittest.TestCase):
super(TestLauncher, cls).tearDownClass()
def testHelp(self):
+ # option -h must cause a raise SystemExit or a return 0
try:
result = view.main(["view", "--help"])
- self.assertNotEqual(result, 0)
except SystemExit as e:
result = e.args[0]
self.assertEqual(result, 0)
@@ -93,7 +110,6 @@ class TestLauncher(unittest.TestCase):
def testWrongOption(self):
try:
result = view.main(["view", "--foo"])
- self.assertNotEqual(result, 0)
except SystemExit as e:
result = e.args[0]
self.assertNotEqual(result, 0)
@@ -101,10 +117,9 @@ class TestLauncher(unittest.TestCase):
def testWrongFile(self):
try:
result = view.main(["view", "__file.not.found__"])
- self.assertNotEqual(result, 0)
except SystemExit as e:
result = e.args[0]
- self.assertNotEqual(result, 0)
+ self.assertEqual(result, 0)
def testFile(self):
# sys.executable is an existing readable file
@@ -118,8 +133,10 @@ class TestLauncher(unittest.TestCase):
class TestViewer(TestCaseQt):
"""Test for Viewer class"""
+ @unittest.skipUnless(with_qt, reason)
def testConstruct(self):
- widget = view.Viewer()
+ if view is not None:
+ widget = view.Viewer()
self.qWaitForWindowExposed(widget)
diff --git a/silx/app/test_.py b/silx/app/test_.py
new file mode 100644
index 0000000..7f95085
--- /dev/null
+++ b/silx/app/test_.py
@@ -0,0 +1,175 @@
+# coding: utf-8
+# /*##########################################################################
+# Copyright (C) 2016 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.
+#
+# ############################################################################*/
+"""Launch unittests of the library"""
+
+__authors__ = ["V. Valls"]
+__license__ = "MIT"
+__date__ = "04/08/2017"
+
+import sys
+import os
+import argparse
+import logging
+import unittest
+
+
+class StreamHandlerUnittestReady(logging.StreamHandler):
+ """The unittest class TestResult redefine sys.stdout/err to capture
+ stdout/err from tests and to display them only when a test fail.
+
+ This class allow to use unittest stdout-capture by using the last sys.stdout
+ and not a cached one.
+ """
+
+ def emit(self, record):
+ """
+ :type record: logging.LogRecord
+ """
+ self.stream = sys.stderr
+ super(StreamHandlerUnittestReady, self).emit(record)
+
+ def flush(self):
+ pass
+
+
+def createBasicHandler():
+ """Create the handler using the basic configuration"""
+ hdlr = StreamHandlerUnittestReady()
+ fs = logging.BASIC_FORMAT
+ dfs = None
+ fmt = logging.Formatter(fs, dfs)
+ hdlr.setFormatter(fmt)
+ return hdlr
+
+
+# Use an handler compatible with unittests, else use_buffer is not working
+for h in logging.root.handlers:
+ logging.root.removeHandler(h)
+logging.root.addHandler(createBasicHandler())
+logging.captureWarnings(True)
+
+_logger = logging.getLogger(__name__)
+"""Module logger"""
+
+
+class TextTestResultWithSkipList(unittest.TextTestResult):
+ """Override default TextTestResult to display list of skipped tests at the
+ end
+ """
+
+ def printErrors(self):
+ unittest.TextTestResult.printErrors(self)
+ # Print skipped tests at the end
+ self.printErrorList("SKIPPED", self.skipped)
+
+
+def main(argv):
+ """
+ Main function to launch the unittests as an application
+
+ :param argv: Command line arguments
+ :returns: exit status
+ """
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("-v", "--verbose", default=0,
+ action="count", dest="verbose",
+ help="Increase verbosity. Option -v prints additional " +
+ "INFO messages. Use -vv for full verbosity, " +
+ "including debug messages and test help strings.")
+ parser.add_argument("-x", "--no-gui", dest="gui", default=True,
+ action="store_false",
+ help="Disable the test of the graphical use interface")
+ parser.add_argument("-g", "--no-opengl", dest="opengl", default=True,
+ action="store_false",
+ help="Disable tests using OpenGL")
+ parser.add_argument("-o", "--no-opencl", dest="opencl", default=True,
+ action="store_false",
+ help="Disable the test of the OpenCL part")
+ parser.add_argument("-l", "--low-mem", dest="low_mem", default=False,
+ action="store_true",
+ help="Disable test with large memory consumption (>100Mbyte")
+ parser.add_argument("--qt-binding", dest="qt_binding", default=None,
+ help="Force using a Qt binding, from 'PyQt4', 'PyQt5', or 'PySide'")
+
+ options = parser.parse_args(argv[1:])
+
+ test_verbosity = 1
+ use_buffer = True
+ if options.verbose == 1:
+ logging.root.setLevel(logging.INFO)
+ _logger.info("Set log level: INFO")
+ test_verbosity = 2
+ use_buffer = False
+ elif options.verbose > 1:
+ logging.root.setLevel(logging.DEBUG)
+ _logger.info("Set log level: DEBUG")
+ test_verbosity = 2
+ use_buffer = False
+
+ if not options.gui:
+ os.environ["WITH_QT_TEST"] = "False"
+
+ if not options.opencl:
+ os.environ["SILX_OPENCL"] = "False"
+
+ if not options.opengl:
+ os.environ["WITH_GL_TEST"] = "False"
+
+ if options.low_mem:
+ os.environ["SILX_TEST_LOW_MEM"] = "True"
+
+ if options.qt_binding:
+ binding = options.qt_binding.lower()
+ if binding == "pyqt4":
+ _logger.info("Force using PyQt4")
+ import PyQt4.QtCore # noqa
+ elif binding == "pyqt5":
+ _logger.info("Force using PyQt5")
+ import PyQt5.QtCore # noqa
+ elif binding == "pyside":
+ _logger.info("Force using PySide")
+ import PySide.QtCore # noqa
+ else:
+ raise ValueError("Qt binding '%s' is unknown" % options.qt_binding)
+
+ # Run the tests
+ runnerArgs = {}
+ runnerArgs["verbosity"] = test_verbosity
+ runnerArgs["buffer"] = use_buffer
+ runner = unittest.TextTestRunner(**runnerArgs)
+ runner.resultclass = TextTestResultWithSkipList
+
+ # Display the result when using CTRL-C
+ unittest.installHandler()
+
+ import silx.test
+ test_suite = unittest.TestSuite()
+ test_suite.addTest(silx.test.suite())
+ result = runner.run(test_suite)
+
+ if result.wasSuccessful():
+ exit_status = 0
+ else:
+ exit_status = 1
+ return exit_status
diff --git a/silx/app/view.py b/silx/app/view.py
index 8fdabde..e8507f4 100644
--- a/silx/app/view.py
+++ b/silx/app/view.py
@@ -1,6 +1,6 @@
# coding: utf-8
# /*##########################################################################
-# Copyright (C) 2016 European Synchrotron Radiation Facility
+# Copyright (C) 2016-2017 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 @@
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "12/04/2017"
+__date__ = "02/10/2017"
import sys
import os
@@ -33,30 +33,10 @@ import argparse
import logging
import collections
-
-logging.basicConfig()
_logger = logging.getLogger(__name__)
"""Module logger"""
-try:
- # it should be loaded before h5py
- import hdf5plugin # noqa
-except ImportError:
- hdf5plugin = None
-
-try:
- import h5py
- import silx.gui.hdf5
-except ImportError:
- h5py = None
-
-try:
- import fabio
-except ImportError:
- fabio = None
-
from silx.gui import qt
-from silx.gui.data.DataViewerFrame import DataViewerFrame
class Viewer(qt.QMainWindow):
@@ -71,6 +51,10 @@ class Viewer(qt.QMainWindow):
:class:`silx.io.spech5.SpecH5` or :class:`h5py.File`
instances)
"""
+ # Import it here to be sure to use the right logging level
+ import silx.gui.hdf5
+ from silx.gui.data.DataViewerFrame import DataViewerFrame
+
qt.QMainWindow.__init__(self)
self.setWindowTitle("Silx viewer")
@@ -97,14 +81,15 @@ class Viewer(qt.QMainWindow):
self.setCentralWidget(main_panel)
- self.__treeview.selectionModel().selectionChanged.connect(self.displayData)
+ model = self.__treeview.selectionModel()
+ model.selectionChanged.connect(self.displayData)
+ self.__treeview.addContextMenuCallback(self.closeAndSyncCustomContextMenu)
- self.__treeview.addContextMenuCallback(self.customContextMenu)
- # lambda function will never be called cause we store it as weakref
- self.__treeview.addContextMenuCallback(lambda event: None)
- # you have to store it first
- self.__store_lambda = lambda event: self.closeAndSyncCustomContextMenu(event)
- self.__treeview.addContextMenuCallback(self.__store_lambda)
+ treeModel = self.__treeview.findHdf5TreeModel()
+ columns = list(treeModel.COLUMN_IDS)
+ columns.remove(treeModel.DESCRIPTION_COLUMN)
+ columns.remove(treeModel.NODE_COLUMN)
+ self.__treeview.header().setSections(columns)
self.createActions()
self.createMenus()
@@ -159,15 +144,17 @@ class Viewer(qt.QMainWindow):
extensions = collections.OrderedDict()
# expect h5py
- extensions["HDF5 files"] = "*.h5"
+ extensions["HDF5 files"] = "*.h5 *.hdf"
+ extensions["NeXus files"] = "*.nx *.nxs *.h5 *.hdf"
# no dependancy
- extensions["Spec files"] = "*.dat *.spec *.mca"
+ extensions["NeXus layout from spec files"] = "*.dat *.spec *.mca"
+ extensions["Numpy binary files"] = "*.npz *.npy"
# expect fabio
- extensions["EDF files"] = "*.edf"
- extensions["TIFF image files"] = "*.tif *.tiff"
- extensions["NumPy binary files"] = "*.npy"
- extensions["CBF files"] = "*.cbf"
- extensions["MarCCD image files"] = "*.mccd"
+ extensions["NeXus layout from raster images"] = "*.edf *.tif *.tiff *.cbf *.mccd"
+ extensions["NeXus layout from EDF files"] = "*.edf"
+ extensions["NeXus layout from TIFF image files"] = "*.tif *.tiff"
+ extensions["NeXus layout from CBF files"] = "*.cbf"
+ extensions["NeXus layout from MarCCD image files"] = "*.mccd"
filters = []
filters.append("All supported files (%s)" % " ".join(extensions.values()))
@@ -180,48 +167,8 @@ class Viewer(qt.QMainWindow):
return dialog
def about(self):
- import silx._version
- message = """<p align="center"><b>Silx viewer</b>
- <br />
- <br />{silx_version}
- <br />
- <br /><a href="{project_url}">Upstream project on GitHub</a>
- </p>
- <p align="left">
- <dl>
- <dt><b>Silx version</b></dt><dd>{silx_version}</dd>
- <dt><b>Qt version</b></dt><dd>{qt_version}</dd>
- <dt><b>Qt binding</b></dt><dd>{qt_binding}</dd>
- <dt><b>Python version</b></dt><dd>{python_version}</dd>
- <dt><b>Optional libraries</b></dt><dd>{optional_lib}</dd>
- </dl>
- </p>
- <p>
- Copyright (C) <a href="{esrf_url}">European Synchrotron Radiation Facility</a>
- </p>
- """
- def format_optional_lib(name, isAvailable):
- if isAvailable:
- template = '<b>%s</b> is <font color="green">installed</font>'
- else:
- template = '<b>%s</b> is <font color="red">not installed</font>'
- return template % name
-
- optional_lib = []
- optional_lib.append(format_optional_lib("FabIO", fabio is not None))
- optional_lib.append(format_optional_lib("H5py", h5py is not None))
- optional_lib.append(format_optional_lib("hdf5plugin", hdf5plugin is not None))
-
- info = dict(
- esrf_url="http://www.esrf.eu",
- project_url="https://github.com/silx-kit/silx",
- silx_version=silx._version.version,
- qt_binding=qt.BINDING,
- qt_version=qt.qVersion(),
- python_version=sys.version.replace("\n", "<br />"),
- optional_lib="<br />".join(optional_lib)
- )
- qt.QMessageBox.about(self, "About Menu", message.format(**info))
+ from . import qtutils
+ qtutils.About.about(self, "Silx viewer")
def appendFile(self, filename):
self.__treeview.findHdf5TreeModel().appendFile(filename)
@@ -229,7 +176,7 @@ class Viewer(qt.QMainWindow):
def displayData(self):
"""Called to update the dataviewer with the selected data.
"""
- selected = list(self.__treeview.selectedH5Nodes())
+ selected = list(self.__treeview.selectedH5Nodes(ignoreBrokenLinks=False))
if len(selected) == 1:
# Update the viewer for a single selection
data = selected[0]
@@ -238,40 +185,20 @@ class Viewer(qt.QMainWindow):
def useAsyncLoad(self, useAsync):
self.__asyncload = useAsync
- def customContextMenu(self, event):
- """Called to populate the context menu
-
- :param silx.gui.hdf5.Hdf5ContextMenuEvent event: Event
- containing expected information to populate the context menu
- """
- selectedObjects = event.source().selectedH5Nodes()
- menu = event.menu()
-
- hasDataset = False
- for obj in selectedObjects:
- if obj.ntype is h5py.Dataset:
- hasDataset = True
- break
-
- if len(menu.children()):
- menu.addSeparator()
-
- if hasDataset:
- action = qt.QAction("Do something on the datasets", event.source())
- menu.addAction(action)
-
def closeAndSyncCustomContextMenu(self, event):
"""Called to populate the context menu
:param silx.gui.hdf5.Hdf5ContextMenuEvent event: Event
containing expected information to populate the context menu
"""
- selectedObjects = event.source().selectedH5Nodes()
+ selectedObjects = event.source().selectedH5Nodes(ignoreBrokenLinks=False)
menu = event.menu()
if len(menu.children()):
menu.addSeparator()
+ # Import it here to be sure to use the right logging level
+ import h5py
for obj in selectedObjects:
if obj.ntype is h5py.File:
action = qt.QAction("Remove %s" % obj.local_filename, event.source())
@@ -292,12 +219,43 @@ def main(argv):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
'files',
- type=argparse.FileType('rb'),
nargs=argparse.ZERO_OR_MORE,
help='Data file to show (h5 file, edf files, spec files)')
+ parser.add_argument(
+ '--debug',
+ dest="debug",
+ action="store_true",
+ default=False,
+ help='Set logging system in debug mode')
+ parser.add_argument(
+ '--use-opengl-plot',
+ dest="use_opengl_plot",
+ action="store_true",
+ default=False,
+ help='Use OpenGL for plots (instead of matplotlib)')
options = parser.parse_args(argv[1:])
+ if options.debug:
+ logging.root.setLevel(logging.DEBUG)
+
+ #
+ # Import most of the things here to be sure to use the right logging level
+ #
+
+ try:
+ # it should be loaded before h5py
+ import hdf5plugin # noqa
+ except ImportError:
+ _logger.debug("Backtrace", exc_info=True)
+ hdf5plugin = None
+
+ try:
+ import h5py
+ except ImportError:
+ _logger.debug("Backtrace", exc_info=True)
+ h5py = None
+
if h5py is None:
message = "Module 'h5py' is not installed but is mandatory."\
+ " You can install it using \"pip install h5py\"."
@@ -309,15 +267,27 @@ def main(argv):
+ " compressions. You can install it using \"pip install hdf5plugin\"."
_logger.warning(message)
+ #
+ # Run the application
+ #
+
+ if options.use_opengl_plot:
+ from silx.gui.plot import PlotWidget
+ PlotWidget.setDefaultBackend("opengl")
+
app = qt.QApplication([])
+ qt.QLocale.setDefault(qt.QLocale.c())
+
sys.excepthook = qt.exceptionHandler
window = Viewer()
window.resize(qt.QSize(640, 480))
- for f in options.files:
- filename = f.name
- f.close()
- window.appendFile(filename)
+ for filename in options.files:
+ try:
+ window.appendFile(filename)
+ except IOError as e:
+ _logger.error(e.args[0])
+ _logger.debug("Backtrace", exc_info=True)
window.show()
result = app.exec_()
diff --git a/silx/gui/_glutils/FramebufferTexture.py b/silx/gui/_glutils/FramebufferTexture.py
index b01eb41..cc05080 100644
--- a/silx/gui/_glutils/FramebufferTexture.py
+++ b/silx/gui/_glutils/FramebufferTexture.py
@@ -66,49 +66,48 @@ class FramebufferTexture(object):
self._previousFramebuffer = 0 # Used by with statement
self._name = gl.glGenFramebuffers(1)
- gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self._name)
-
- # Attachments
- gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER,
- gl.GL_COLOR_ATTACHMENT0,
- gl.GL_TEXTURE_2D,
- self._texture.name,
- 0)
-
- height, width = self._texture.shape
-
- if stencilFormat is not None:
- self._stencilId = gl.glGenRenderbuffers(1)
- gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, self._stencilId)
- gl.glRenderbufferStorage(gl.GL_RENDERBUFFER,
- stencilFormat,
- width, height)
- gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER,
- gl.GL_STENCIL_ATTACHMENT,
- gl.GL_RENDERBUFFER,
- self._stencilId)
- else:
- self._stencilId = None
- if depthFormat is not None:
- if self._stencilId and depthFormat in self._PACKED_FORMAT:
- self._depthId = self._stencilId
- else:
- self._depthId = gl.glGenRenderbuffers(1)
- gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, self._depthId)
+ with self: # Bind FBO
+ # Attachments
+ gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER,
+ gl.GL_COLOR_ATTACHMENT0,
+ gl.GL_TEXTURE_2D,
+ self._texture.name,
+ 0)
+
+ height, width = self._texture.shape
+
+ if stencilFormat is not None:
+ self._stencilId = gl.glGenRenderbuffers(1)
+ gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, self._stencilId)
gl.glRenderbufferStorage(gl.GL_RENDERBUFFER,
- depthFormat,
+ stencilFormat,
width, height)
- gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER,
- gl.GL_DEPTH_ATTACHMENT,
- gl.GL_RENDERBUFFER,
- self._depthId)
- else:
- self._depthId = None
+ gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER,
+ gl.GL_STENCIL_ATTACHMENT,
+ gl.GL_RENDERBUFFER,
+ self._stencilId)
+ else:
+ self._stencilId = None
+
+ if depthFormat is not None:
+ if self._stencilId and depthFormat in self._PACKED_FORMAT:
+ self._depthId = self._stencilId
+ else:
+ self._depthId = gl.glGenRenderbuffers(1)
+ gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, self._depthId)
+ gl.glRenderbufferStorage(gl.GL_RENDERBUFFER,
+ depthFormat,
+ width, height)
+ gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER,
+ gl.GL_DEPTH_ATTACHMENT,
+ gl.GL_RENDERBUFFER,
+ self._depthId)
+ else:
+ self._depthId = None
- assert gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) == \
- gl.GL_FRAMEBUFFER_COMPLETE
- gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
+ assert (gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) ==
+ gl.GL_FRAMEBUFFER_COMPLETE)
@property
def shape(self):
@@ -143,6 +142,7 @@ class FramebufferTexture(object):
def __exit__(self, exctype, excvalue, traceback):
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self._previousFramebuffer)
+ self._previousFramebuffer = None
def discard(self):
"""Delete associated OpenGL resources including texture"""
diff --git a/silx/gui/_glutils/OpenGLWidget.py b/silx/gui/_glutils/OpenGLWidget.py
new file mode 100644
index 0000000..6cbf8f0
--- /dev/null
+++ b/silx/gui/_glutils/OpenGLWidget.py
@@ -0,0 +1,409 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2017 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 package provides a compatibility layer for OpenGL widget.
+
+It provides a compatibility layer for Qt OpenGL widget used in silx
+across Qt<=5.3 QtOpenGL.QGLWidget and QOpenGLWidget.
+"""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "26/07/2017"
+
+
+import logging
+import sys
+
+from .. import qt
+from .._glutils import gl
+
+
+_logger = logging.getLogger(__name__)
+
+
+# Probe OpenGL availability and widget
+ERROR = '' # Error message from probing Qt OpenGL support
+_BaseOpenGLWidget = None # Qt OpenGL widget to use
+
+if hasattr(qt, 'QOpenGLWidget'): # PyQt>=5.4
+ _logger.info('Using QOpenGLWidget')
+ _BaseOpenGLWidget = qt.QOpenGLWidget
+
+elif not qt.HAS_OPENGL: # QtOpenGL not installed
+ ERROR = '%s.QtOpenGL not available' % qt.BINDING
+
+elif qt.QApplication.instance() and not qt.QGLFormat.hasOpenGL():
+ # qt.QGLFormat.hasOpenGL MUST be called with a QApplication created
+ # so this is only checked if the QApplication is already created
+ ERROR = 'Qt reports OpenGL not available'
+
+else:
+ _logger.info('Using QGLWidget')
+ _BaseOpenGLWidget = qt.QGLWidget
+
+
+# Internal class wrapping Qt OpenGL widget
+if _BaseOpenGLWidget is None:
+ _logger.error('OpenGL-based widget disabled: %s', ERROR)
+ _OpenGLWidget = None
+
+else:
+ class _OpenGLWidget(_BaseOpenGLWidget):
+ """Wrapper over QOpenGLWidget and QGLWidget"""
+
+ sigOpenGLContextError = qt.Signal(str)
+ """Signal emitted when an OpenGL context error is detected at runtime.
+
+ It provides the error reason as a str.
+ """
+
+ def __init__(self, parent,
+ alphaBufferSize=0,
+ depthBufferSize=24,
+ stencilBufferSize=8,
+ version=(2, 0),
+ f=qt.Qt.WindowFlags()):
+ # True if using QGLWidget, False if using QOpenGLWidget
+ self.__legacy = not hasattr(qt, 'QOpenGLWidget')
+
+ self.__devicePixelRatio = 1.0
+ self.__requestedOpenGLVersion = int(version[0]), int(version[1])
+ self.__isValid = False
+
+ if self.__legacy: # QGLWidget
+ format_ = qt.QGLFormat()
+ format_.setAlphaBufferSize(alphaBufferSize)
+ format_.setAlpha(alphaBufferSize != 0)
+ format_.setDepthBufferSize(depthBufferSize)
+ format_.setDepth(depthBufferSize != 0)
+ format_.setStencilBufferSize(stencilBufferSize)
+ format_.setStencil(stencilBufferSize != 0)
+ format_.setVersion(*self.__requestedOpenGLVersion)
+ format_.setDoubleBuffer(True)
+
+ super(_OpenGLWidget, self).__init__(format_, parent, None, f)
+
+ else: # QOpenGLWidget
+ super(_OpenGLWidget, self).__init__(parent, f)
+
+ format_ = qt.QSurfaceFormat()
+ format_.setAlphaBufferSize(alphaBufferSize)
+ format_.setDepthBufferSize(depthBufferSize)
+ format_.setStencilBufferSize(stencilBufferSize)
+ format_.setVersion(*self.__requestedOpenGLVersion)
+ format_.setSwapBehavior(qt.QSurfaceFormat.DoubleBuffer)
+ self.setFormat(format_)
+
+
+ def getDevicePixelRatio(self):
+ """Returns the ratio device-independent / device pixel size
+
+ It should be either 1.0 or 2.0.
+
+ :return: Scale factor between screen and Qt units
+ :rtype: float
+ """
+ return self.__devicePixelRatio
+
+ def getRequestedOpenGLVersion(self):
+ """Returns the requested OpenGL version.
+
+ :return: (major, minor)
+ :rtype: 2-tuple of int"""
+ return self.__requestedOpenGLVersion
+
+ def getOpenGLVersion(self):
+ """Returns the available OpenGL version.
+
+ :return: (major, minor)
+ :rtype: 2-tuple of int"""
+ if self.__legacy: # QGLWidget
+ supportedVersion = 0, 0
+
+ # Go through all OpenGL version flags checking support
+ flags = self.format().openGLVersionFlags()
+ for version in ((1, 1), (1, 2), (1, 3), (1, 4), (1, 5),
+ (2, 0), (2, 1),
+ (3, 0), (3, 1), (3, 2), (3, 3),
+ (4, 0)):
+ versionFlag = getattr(qt.QGLFormat,
+ 'OpenGL_Version_%d_%d' % version)
+ if not versionFlag & flags:
+ break
+ supportedVersion = version
+ return supportedVersion
+
+ else: # QOpenGLWidget
+ return self.format().version()
+
+ # QOpenGLWidget methods
+
+ def isValid(self):
+ """Returns True if OpenGL is available.
+
+ This adds extra checks to Qt isValid method.
+
+ :rtype: bool
+ """
+ return self.__isValid and super(_OpenGLWidget, self).isValid()
+
+ def defaultFramebufferObject(self):
+ """Returns the framebuffer object handle.
+
+ See :meth:`QOpenGLWidget.defaultFramebufferObject`
+ """
+ if self.__legacy: # QGLWidget
+ return 0
+ else: # QOpenGLWidget
+ return super(_OpenGLWidget, self).defaultFramebufferObject()
+
+ # *GL overridden methods
+
+ def initializeGL(self):
+ parent = self.parent()
+ if parent is None:
+ _logger.error('_OpenGLWidget has no parent')
+ return
+
+ # Check OpenGL version
+ if self.getOpenGLVersion() >= self.getRequestedOpenGLVersion():
+ version = gl.glGetString(gl.GL_VERSION)
+ if version:
+ self.__isValid = True
+ else:
+ errMsg = 'OpenGL not available'
+ if sys.platform.startswith('linux'):
+ errMsg += ': If connected remotely, ' \
+ 'GLX forwarding might be disabled.'
+ _logger.error(errMsg)
+ self.sigOpenGLContextError.emit(errMsg)
+ self.__isValid = False
+
+ else:
+ errMsg = 'OpenGL %d.%d not available' % \
+ self.getRequestedOpenGLVersion()
+ _logger.error('OpenGL widget disabled: %s', errMsg)
+ self.sigOpenGLContextError.emit(errMsg)
+ self.__isValid = False
+
+ if self.isValid():
+ parent.initializeGL()
+
+ def paintGL(self):
+ parent = self.parent()
+ if parent is None:
+ _logger.error('_OpenGLWidget has no parent')
+ return
+
+ if qt.BINDING == 'PyQt5':
+ devicePixelRatio = self.window().windowHandle().devicePixelRatio()
+
+ if devicePixelRatio != self.getDevicePixelRatio():
+ # Update devicePixelRatio and call resizeOpenGL
+ # as resizeGL is not always called.
+ self.__devicePixelRatio = devicePixelRatio
+ self.makeCurrent()
+ parent.resizeGL(self.width(), self.height())
+
+ if self.isValid():
+ parent.paintGL()
+
+ def resizeGL(self, width, height):
+ parent = self.parent()
+ if parent is None:
+ _logger.error('_OpenGLWidget has no parent')
+ return
+
+ if self.isValid():
+ # Call parent resizeGL with device-independent pixel unit
+ # This works over both QGLWidget and QOpenGLWidget
+ parent.resizeGL(self.width(), self.height())
+
+
+class OpenGLWidget(qt.QWidget):
+ """OpenGL widget wrapper over QGLWidget and QOpenGLWidget
+
+ This wrapper API implements a subset of QOpenGLWidget API.
+ The constructor takes a different set of arguments.
+ Methods returning object like :meth:`context` returns either
+ QGL* or QOpenGL* objects.
+
+ :param parent: Parent widget see :class:`QWidget`
+ :param int alphaBufferSize:
+ Size in bits of the alpha channel (default: 0).
+ Set to 0 to disable alpha channel.
+ :param int depthBufferSize:
+ Size in bits of the depth buffer (default: 24).
+ Set to 0 to disable depth buffer.
+ :param int stencilBufferSize:
+ Size in bits of the stencil buffer (default: 8).
+ Set to 0 to disable stencil buffer
+ :param version: Requested OpenGL version (default: (2, 0)).
+ :type version: 2-tuple of int
+ :param f: see :class:`QWidget`
+ """
+
+ def __init__(self, parent=None,
+ alphaBufferSize=0,
+ depthBufferSize=24,
+ stencilBufferSize=8,
+ version=(2, 0),
+ f=qt.Qt.WindowFlags()):
+ super(OpenGLWidget, self).__init__(parent, f)
+
+ layout = qt.QHBoxLayout(self)
+ layout.setContentsMargins(0, 0, 0, 0)
+ self.setLayout(layout)
+
+ if _OpenGLWidget is None:
+ self.__openGLWidget = None
+ label = self._createErrorQLabel(ERROR)
+ self.layout().addWidget(label)
+
+ else:
+ self.__openGLWidget = _OpenGLWidget(
+ parent=self,
+ alphaBufferSize=alphaBufferSize,
+ depthBufferSize=depthBufferSize,
+ stencilBufferSize=stencilBufferSize,
+ version=version,
+ f=f)
+ # Async connection need, otherwise issue when hiding OpenGL
+ # widget while doing the rendering..
+ self.__openGLWidget.sigOpenGLContextError.connect(
+ self._handleOpenGLInitError, qt.Qt.QueuedConnection)
+ self.layout().addWidget(self.__openGLWidget)
+
+ @staticmethod
+ def _createErrorQLabel(error):
+ """Create QLabel displaying error message in place of OpenGL widget
+
+ :param str error: The error message to display"""
+ label = qt.QLabel()
+ label.setText('OpenGL-based widget disabled:\n%s' % error)
+ label.setAlignment(qt.Qt.AlignCenter)
+ label.setWordWrap(True)
+ return label
+
+ def _handleOpenGLInitError(self, error):
+ """Handle runtime errors in OpenGL widget"""
+ if self.__openGLWidget is not None:
+ self.__openGLWidget.setVisible(False)
+ self.__openGLWidget.setParent(None)
+ self.__openGLWidget = None
+
+ label = self._createErrorQLabel(error)
+ self.layout().addWidget(label)
+
+ # Additional API
+
+ def getDevicePixelRatio(self):
+ """Returns the ratio device-independent / device pixel size
+
+ It should be either 1.0 or 2.0.
+
+ :return: Scale factor between screen and Qt units
+ :rtype: float
+ """
+ if self.__openGLWidget is None:
+ return 1.
+ else:
+ return self.__openGLWidget.getDevicePixelRatio()
+
+ def getOpenGLVersion(self):
+ """Returns the available OpenGL version.
+
+ :return: (major, minor)
+ :rtype: 2-tuple of int"""
+ if self.__openGLWidget is None:
+ return 0, 0
+ else:
+ return self.__openGLWidget.getOpenGLVersion()
+
+ # QOpenGLWidget API
+
+ def isValid(self):
+ """Returns True if OpenGL with the requested version is available.
+
+ :rtype: bool
+ """
+ if self.__openGLWidget is None:
+ return False
+ else:
+ return self.__openGLWidget.isValid()
+
+ def context(self):
+ """Return Qt OpenGL context object or None.
+
+ See :meth:`QOpenGLWidget.context` and :meth:`QGLWidget.context`
+ """
+ if self.__openGLWidget is None:
+ return None
+ else:
+ return self.__openGLWidget.context()
+
+ def defaultFramebufferObject(self):
+ """Returns the framebuffer object handle.
+
+ See :meth:`QOpenGLWidget.defaultFramebufferObject`
+ """
+ if self.__openGLWidget is None:
+ return 0
+ else:
+ return self.__openGLWidget.defaultFramebufferObject()
+
+ def makeCurrent(self):
+ """Make the underlying OpenGL widget's context current.
+
+ See :meth:`QOpenGLWidget.makeCurrent`
+ """
+ if self.__openGLWidget is not None:
+ self.__openGLWidget.makeCurrent()
+
+ def update(self):
+ """Async update of the OpenGL widget.
+
+ See :meth:`QOpenGLWidget.update`
+ """
+ if self.__openGLWidget is not None:
+ self.__openGLWidget.update()
+
+ # QOpenGLWidget API to override
+
+ def initializeGL(self):
+ """Override to implement OpenGL initialization."""
+ pass
+
+ def paintGL(self):
+ """Override to implement OpenGL rendering."""
+ pass
+
+ def resizeGL(self, width, height):
+ """Override to implement resize of OpenGL framebuffer.
+
+ :param int width: Width in device-independent pixels
+ :param int height: Height in device-independent pixels
+ """
+ pass
diff --git a/silx/gui/_glutils/VertexBuffer.py b/silx/gui/_glutils/VertexBuffer.py
index 689b543..b74b748 100644
--- a/silx/gui/_glutils/VertexBuffer.py
+++ b/silx/gui/_glutils/VertexBuffer.py
@@ -180,7 +180,7 @@ class VertexBufferAttrib(object):
dimension=1,
offset=0,
stride=0,
- normalisation=False):
+ normalization=False):
self.vbo = vbo
assert type_ in self._GL_TYPES
self.type_ = type_
@@ -189,7 +189,7 @@ class VertexBufferAttrib(object):
self.dimension = dimension
self.offset = offset
self.stride = stride
- self.normalisation = bool(normalisation)
+ self.normalization = bool(normalization)
@property
def itemsize(self):
@@ -200,12 +200,12 @@ class VertexBufferAttrib(object):
def setVertexAttrib(self, attribute):
"""Call glVertexAttribPointer with objects information"""
- normalisation = gl.GL_TRUE if self.normalisation else gl.GL_FALSE
+ normalization = gl.GL_TRUE if self.normalization else gl.GL_FALSE
with self.vbo:
gl.glVertexAttribPointer(attribute,
self.dimension,
self.type_,
- normalisation,
+ normalization,
self.stride,
c_void_p(self.offset))
@@ -216,7 +216,7 @@ class VertexBufferAttrib(object):
self.dimension,
self.offset,
self.stride,
- self.normalisation)
+ self.normalization)
def vertexBuffer(arrays, prefix=None, suffix=None, usage=None):
diff --git a/silx/gui/_glutils/__init__.py b/silx/gui/_glutils/__init__.py
index e86a58f..15e48e1 100644
--- a/silx/gui/_glutils/__init__.py
+++ b/silx/gui/_glutils/__init__.py
@@ -33,6 +33,7 @@ __date__ = "25/07/2016"
# OpenGL convenient functions
+from .OpenGLWidget import OpenGLWidget # noqa
from .Context import getGLContext, setGLContextGetter # noqa
from .FramebufferTexture import FramebufferTexture # noqa
from .Program import Program # noqa
diff --git a/silx/gui/_glutils/font.py b/silx/gui/_glutils/font.py
index 566ae49..2be2c04 100644
--- a/silx/gui/_glutils/font.py
+++ b/silx/gui/_glutils/font.py
@@ -98,27 +98,39 @@ def rasterText(text, font,
_logger.info("Trying to raster empty text, replaced by white space")
text = ' ' # Replace empty text by white space to produce an image
+ if (devicePixelRatio != 1.0 and
+ not hasattr(qt.QImage, 'setDevicePixelRatio')): # Qt 4
+ _logger.error('devicePixelRatio not supported')
+ devicePixelRatio = 1.0
+
if not isinstance(font, qt.QFont):
font = qt.QFont(font, size, weight, italic)
+ # get text size
+ image = qt.QImage(1, 1, qt.QImage.Format_RGB888)
+ painter = qt.QPainter()
+ painter.begin(image)
+ painter.setPen(qt.Qt.white)
+ painter.setFont(font)
+ bounds = painter.boundingRect(
+ qt.QRect(0, 0, 4096, 4096), qt.Qt.TextExpandTabs, text)
+ painter.end()
+
metrics = qt.QFontMetrics(font)
- size = metrics.size(qt.Qt.TextExpandTabs, text)
- bounds = metrics.boundingRect(
- qt.QRect(0, 0, size.width(), size.height()),
- qt.Qt.TextExpandTabs,
- text)
- if (devicePixelRatio != 1.0 and
- not hasattr(qt.QImage, 'setDevicePixelRatio')): # Qt 4
- _logger.error('devicePixelRatio not supported')
- devicePixelRatio = 1.0
+ # This does not provide the correct text bbox on macOS
+ # size = metrics.size(qt.Qt.TextExpandTabs, text)
+ # bounds = metrics.boundingRect(
+ # qt.QRect(0, 0, size.width(), size.height()),
+ # qt.Qt.TextExpandTabs,
+ # text)
# Add extra border and handle devicePixelRatio
width = bounds.width() * devicePixelRatio + 2
# align line size to 32 bits to ease conversion to numpy array
width = 4 * ((width + 3) // 4)
image = qt.QImage(width,
- bounds.height() * devicePixelRatio,
+ bounds.height() * devicePixelRatio + 2,
qt.QImage.Format_RGB888)
if (devicePixelRatio != 1.0 and
hasattr(image, 'setDevicePixelRatio')): # Qt 5
diff --git a/silx/gui/console.py b/silx/gui/console.py
index 13760b4..7812e2d 100644
--- a/silx/gui/console.py
+++ b/silx/gui/console.py
@@ -136,6 +136,8 @@ if qtconsole is None:
class IPythonWidget(RichIPythonWidget):
"""Live IPython console widget.
+ .. image:: img/IPythonWidget.png
+
:param custom_banner: Custom welcome message to be printed at the top of
the console.
"""
@@ -175,6 +177,8 @@ class IPythonDockWidget(qt.QDockWidget):
"""Dock Widget including a :class:`IPythonWidget` inside
a vertical layout.
+ .. image:: img/IPythonDockWidget.png
+
:param available_vars: Dictionary of variables to be pushed to the
console's interactive namespace: ``{"variable_name": object, …}``
:param custom_banner: Custom welcome message to be printed at the top of
diff --git a/silx/gui/data/ArrayTableModel.py b/silx/gui/data/ArrayTableModel.py
index 87a2fc1..ad4d33a 100644
--- a/silx/gui/data/ArrayTableModel.py
+++ b/silx/gui/data/ArrayTableModel.py
@@ -34,7 +34,7 @@ from silx.gui.data.TextFormatter import TextFormatter
__authors__ = ["V.A. Sole"]
__license__ = "MIT"
-__date__ = "24/01/2017"
+__date__ = "27/09/2017"
_logger = logging.getLogger(__name__)
@@ -191,7 +191,7 @@ class ArrayTableModel(qt.QAbstractTableModel):
selection = self._getIndexTuple(index.row(),
index.column())
if role == qt.Qt.DisplayRole:
- return self._formatter.toString(self._array[selection])
+ return self._formatter.toString(self._array[selection], self._array.dtype)
if role == qt.Qt.BackgroundRole and self._bgcolors is not None:
r, g, b = self._bgcolors[selection][0:3]
@@ -296,6 +296,9 @@ class ArrayTableModel(qt.QAbstractTableModel):
elif copy:
# copy requested (default)
self._array = numpy.array(data, copy=True)
+ if hasattr(data, "dtype"):
+ # Avoid to lose the monkey-patched h5py dtype
+ self._array.dtype = data.dtype
elif not _is_array(data):
raise TypeError("data is not a proper array. Try setting" +
" copy=True to convert it into a numpy array" +
diff --git a/silx/gui/data/ArrayTableWidget.py b/silx/gui/data/ArrayTableWidget.py
index ba3fa11..cb8e915 100644
--- a/silx/gui/data/ArrayTableWidget.py
+++ b/silx/gui/data/ArrayTableWidget.py
@@ -230,6 +230,8 @@ class ArrayTableWidget(qt.QWidget):
To select the perspective, use :meth:`setPerspective` or
use :meth:`setFrameAxes`.
To select the frame, use :meth:`setFrameIndex`.
+
+ .. image:: img/ArrayTableWidget.png
"""
def __init__(self, parent=None):
"""
diff --git a/silx/gui/data/DataViewer.py b/silx/gui/data/DataViewer.py
index 3a3ac64..750c654 100644
--- a/silx/gui/data/DataViewer.py
+++ b/silx/gui/data/DataViewer.py
@@ -22,8 +22,8 @@
# THE SOFTWARE.
#
# ###########################################################################*/
-"""This module defines a widget designed to display data using to most adapted
-view from available ones from silx.
+"""This module defines a widget designed to display data using the most adapted
+view from the ones provided by silx.
"""
from __future__ import division
@@ -35,7 +35,7 @@ from silx.gui.data.NumpyAxesSelector import NumpyAxesSelector
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "26/04/2017"
+__date__ = "03/10/2017"
_logger = logging.getLogger(__name__)
@@ -144,7 +144,7 @@ class DataViewer(qt.QFrame):
DataViews._Hdf5View,
DataViews._NXdataView,
DataViews._Plot1dView,
- DataViews._Plot2dView,
+ DataViews._ImageView,
DataViews._Plot3dView,
DataViews._RawView,
DataViews._StackView,
@@ -201,7 +201,7 @@ class DataViewer(qt.QFrame):
self.__numpySelection.clear()
info = DataViews.DataInfo(self.__data)
axisNames = self.__currentView.axesNames(self.__data, info)
- if info.isArray and self.__data is not None and len(axisNames) > 0:
+ 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())
diff --git a/silx/gui/data/DataViewerFrame.py b/silx/gui/data/DataViewerFrame.py
index b48fa7b..e050d4a 100644
--- a/silx/gui/data/DataViewerFrame.py
+++ b/silx/gui/data/DataViewerFrame.py
@@ -27,7 +27,7 @@
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "10/04/2017"
+__date__ = "21/09/2017"
from silx.gui import qt
from .DataViewer import DataViewer
@@ -79,6 +79,14 @@ class DataViewerFrame(qt.QWidget):
"""Avoid to create views while the instance is not created."""
super(_DataViewer, self)._initializeViews()
+ def _createDefaultViews(self, parent):
+ """Expose the original `createDefaultViews` function"""
+ return super(_DataViewer, self).createDefaultViews()
+
+ def createDefaultViews(self, parent=None):
+ """Allow the DataViewerFrame to override this function"""
+ return self.parent().createDefaultViews(parent)
+
self.__dataViewer = _DataViewer(self)
# initialize views when `self.__dataViewer` is set
self.__dataViewer.initializeViews()
@@ -127,7 +135,7 @@ class DataViewerFrame(qt.QWidget):
:param QWidget parent: QWidget parent of the views
:rtype: list[silx.gui.data.DataViews.DataView]
"""
- return self.__dataViewer.createDefaultViews(parent)
+ return self.__dataViewer._createDefaultViews(parent)
def addView(self, view):
"""Allow to add a view to the dataview.
diff --git a/silx/gui/data/DataViews.py b/silx/gui/data/DataViews.py
index d8d605a..1ad997b 100644
--- a/silx/gui/data/DataViews.py
+++ b/silx/gui/data/DataViews.py
@@ -25,6 +25,7 @@
"""This module defines a views used by :class:`silx.gui.data.DataViewer`.
"""
+from collections import OrderedDict
import logging
import numbers
import numpy
@@ -34,11 +35,11 @@ from silx.gui import qt, icons
from silx.gui.data.TextFormatter import TextFormatter
from silx.io import nxdata
from silx.gui.hdf5 import H5Node
-from silx.io.nxdata import NXdata
+from silx.io.nxdata import NXdata, get_attr_as_string
__authors__ = ["V. Valls", "P. Knobel"]
__license__ = "MIT"
-__date__ = "07/04/2017"
+__date__ = "03/10/2017"
_logger = logging.getLogger(__name__)
@@ -52,6 +53,7 @@ RAW_MODE = 40
RAW_ARRAY_MODE = 41
RAW_RECORD_MODE = 42
RAW_SCALAR_MODE = 43
+RAW_HEXA_MODE = 44
STACK_MODE = 50
HDF5_MODE = 60
@@ -62,6 +64,8 @@ def _normalizeData(data):
If the data embed a numpy data or a dataset it is returned.
Else returns the input data."""
if isinstance(data, H5Node):
+ if data.is_broken:
+ return None
return data.h5py_object
return data
@@ -89,11 +93,14 @@ class DataInfo(object):
self.isArray = False
self.interpretation = None
self.isNumeric = False
+ self.isVoid = False
self.isComplex = False
+ self.isBoolean = False
self.isRecord = False
self.isNXdata = False
self.shape = tuple()
self.dim = 0
+ self.size = 0
if data is None:
return
@@ -110,23 +117,32 @@ class DataInfo(object):
self.isArray = False
if silx.io.is_dataset(data):
- self.interpretation = data.attrs.get("interpretation", None)
+ if "interpretation" in data.attrs:
+ self.interpretation = get_attr_as_string(data, "interpretation")
+ else:
+ self.interpretation = None
elif self.isNXdata:
self.interpretation = nxd.interpretation
else:
self.interpretation = None
if hasattr(data, "dtype"):
+ if numpy.issubdtype(data.dtype, numpy.void):
+ # That's a real opaque type, else it is a structured type
+ self.isVoid = data.dtype.fields is None
self.isNumeric = numpy.issubdtype(data.dtype, numpy.number)
self.isRecord = data.dtype.fields is not None
self.isComplex = numpy.issubdtype(data.dtype, numpy.complex)
+ self.isBoolean = numpy.issubdtype(data.dtype, numpy.bool_)
elif self.isNXdata:
self.isNumeric = numpy.issubdtype(nxd.signal.dtype,
numpy.number)
self.isComplex = numpy.issubdtype(nxd.signal.dtype, numpy.complex)
+ self.isBoolean = numpy.issubdtype(nxd.signal.dtype, numpy.bool_)
else:
self.isNumeric = isinstance(data, numbers.Number)
self.isComplex = isinstance(data, numbers.Complex)
+ self.isBoolean = isinstance(data, bool)
self.isRecord = False
if hasattr(data, "shape"):
@@ -135,7 +151,13 @@ class DataInfo(object):
self.shape = nxd.signal.shape
else:
self.shape = tuple()
- self.dim = len(self.shape)
+ if self.shape is not None:
+ self.dim = len(self.shape)
+
+ if hasattr(data, "size"):
+ self.size = int(data.size)
+ else:
+ self.size = 1
def normalizeData(self, data):
"""Returns a normalized data if the embed a numpy or a dataset.
@@ -237,12 +259,12 @@ class DataView(object):
def axesNames(self, data, info):
"""Returns names of the expected axes of the view, according to the
- input data.
+ input data. A none value will disable the default axes selectior.
:param data: Data to display
:type data: numpy.ndarray or h5py.Dataset
:param DataInfo info: Pre-computed information on the data
- :rtype: list[str]
+ :rtype: list[str] or None
"""
return []
@@ -276,7 +298,7 @@ class CompositeDataView(DataView):
:param qt.QWidget parent: Parent of the hold widget
"""
super(CompositeDataView, self).__init__(parent, modeId, icon, label)
- self.__views = {}
+ self.__views = OrderedDict()
self.__currentView = None
def addView(self, dataView):
@@ -285,10 +307,9 @@ class CompositeDataView(DataView):
def getBestView(self, data, info):
"""Returns the best view according to priorities."""
- info = DataInfo(data)
views = [(v.getDataPriority(data, info), v) for v in self.__views.keys()]
views = filter(lambda t: t[0] > DataView.UNSUPPORTED, views)
- views = sorted(views, reverse=True)
+ views = sorted(views, key=lambda t: t[0], reverse=True)
if len(views) == 0:
return None
@@ -361,7 +382,7 @@ class _EmptyView(DataView):
DataView.__init__(self, parent, modeId=EMPTY_MODE)
def axesNames(self, data, info):
- return []
+ return None
def createWidget(self, parent):
return qt.QLabel(parent)
@@ -406,6 +427,8 @@ class _Plot1dView(DataView):
return ["y"]
def getDataPriority(self, data, info):
+ if info.size <= 0:
+ return DataView.UNSUPPORTED
if data is None or not info.isArray or not info.isNumeric:
return DataView.UNSUPPORTED
if info.dim < 1:
@@ -434,9 +457,10 @@ class _Plot2dView(DataView):
def createWidget(self, parent):
from silx.gui import plot
widget = plot.Plot2D(parent=parent)
+ widget.getIntensityHistogramAction().setVisible(True)
widget.setKeepDataAspectRatio(True)
- widget.setGraphXLabel('X')
- widget.setGraphYLabel('Y')
+ widget.getXAxis().setLabel('X')
+ widget.getYAxis().setLabel('Y')
return widget
def clear(self):
@@ -459,7 +483,11 @@ class _Plot2dView(DataView):
return ["y", "x"]
def getDataPriority(self, data, info):
- if data is None or not info.isArray or not info.isNumeric:
+ if info.size <= 0:
+ return DataView.UNSUPPORTED
+ if (data is None or
+ not info.isArray or
+ not (info.isNumeric or info.isBoolean)):
return DataView.UNSUPPORTED
if info.dim < 2:
return DataView.UNSUPPORTED
@@ -494,8 +522,15 @@ class _Plot3dView(DataView):
plot = ScalarFieldView.ScalarFieldView(parent)
plot.setAxesLabels(*reversed(self.axesNames(None, None)))
- plot.addIsosurface(
- lambda data: numpy.mean(data) + numpy.std(data), '#FF0000FF')
+
+ def computeIsolevel(data):
+ data = data[numpy.isfinite(data)]
+ if len(data) == 0:
+ return 0
+ else:
+ return numpy.mean(data) + numpy.std(data)
+
+ plot.addIsosurface(computeIsolevel, '#FF0000FF')
# Create a parameter tree for the scalar field view
options = SFViewParamTree.TreeView(plot)
@@ -527,6 +562,8 @@ class _Plot3dView(DataView):
return ["z", "y", "x"]
def getDataPriority(self, data, info):
+ if info.size <= 0:
+ return DataView.UNSUPPORTED
if data is None or not info.isArray or not info.isNumeric:
return DataView.UNSUPPORTED
if info.dim < 3:
@@ -539,6 +576,54 @@ class _Plot3dView(DataView):
return 10
+class _ComplexImageView(DataView):
+ """View displaying data using a ComplexImageView"""
+
+ def __init__(self, parent):
+ super(_ComplexImageView, self).__init__(
+ parent=parent,
+ modeId=PLOT2D_MODE,
+ label="Complex Image",
+ icon=icons.getQIcon("view-2d"))
+
+ def createWidget(self, parent):
+ from silx.gui.plot.ComplexImageView import ComplexImageView
+ widget = ComplexImageView(parent=parent)
+ widget.getPlot().getIntensityHistogramAction().setVisible(True)
+ widget.getPlot().setKeepDataAspectRatio(True)
+ widget.getXAxis().setLabel('X')
+ widget.getYAxis().setLabel('Y')
+ return widget
+
+ def clear(self):
+ self.getWidget().setData(None)
+
+ def normalizeData(self, data):
+ data = DataView.normalizeData(self, data)
+ return data
+
+ def setData(self, data):
+ data = self.normalizeData(data)
+ self.getWidget().setData(data)
+
+ def axesNames(self, data, info):
+ return ["y", "x"]
+
+ def getDataPriority(self, data, info):
+ if info.size <= 0:
+ return DataView.UNSUPPORTED
+ if data is None or not info.isArray or not info.isComplex:
+ return DataView.UNSUPPORTED
+ if info.dim < 2:
+ return DataView.UNSUPPORTED
+ if info.interpretation == "image":
+ return 1000
+ if info.dim == 2:
+ return 200
+ else:
+ return 190
+
+
class _ArrayView(DataView):
"""View displaying data using a 2d table"""
@@ -562,6 +647,8 @@ class _ArrayView(DataView):
return ["col", "row"]
def getDataPriority(self, data, info):
+ if info.size <= 0:
+ return DataView.UNSUPPORTED
if data is None or not info.isArray or info.isRecord:
return DataView.UNSUPPORTED
if info.dim < 2:
@@ -618,6 +705,8 @@ class _StackView(DataView):
return ["depth", "y", "x"]
def getDataPriority(self, data, info):
+ if info.size <= 0:
+ return DataView.UNSUPPORTED
if data is None or not info.isArray or not info.isNumeric:
return DataView.UNSUPPORTED
if info.dim < 3:
@@ -644,17 +733,21 @@ class _ScalarView(DataView):
self.getWidget().setText("")
def setData(self, data):
- data = self.normalizeData(data)
- if silx.io.is_dataset(data):
- data = data[()]
- text = self.__formatter.toString(data)
+ d = self.normalizeData(data)
+ if silx.io.is_dataset(d):
+ d = d[()]
+ text = self.__formatter.toString(d, data.dtype)
self.getWidget().setText(text)
def axesNames(self, data, info):
return []
def getDataPriority(self, data, info):
+ if info.size <= 0:
+ return DataView.UNSUPPORTED
data = self.normalizeData(data)
+ if info.shape is None:
+ return DataView.UNSUPPORTED
if data is None:
return DataView.UNSUPPORTED
if silx.io.is_group(data):
@@ -681,13 +774,16 @@ class _RecordView(DataView):
data = self.normalizeData(data)
widget = self.getWidget()
widget.setArrayData(data)
- widget.resizeRowsToContents()
- widget.resizeColumnsToContents()
+ if len(data) < 100:
+ widget.resizeRowsToContents()
+ widget.resizeColumnsToContents()
def axesNames(self, data, info):
return ["data"]
def getDataPriority(self, data, info):
+ if info.size <= 0:
+ return DataView.UNSUPPORTED
if info.isRecord:
return 40
if data is None or not info.isArray:
@@ -703,6 +799,36 @@ class _RecordView(DataView):
return DataView.UNSUPPORTED
+class _HexaView(DataView):
+ """View displaying data using text"""
+
+ def __init__(self, parent):
+ DataView.__init__(self, parent, modeId=RAW_HEXA_MODE)
+
+ def createWidget(self, parent):
+ from .HexaTableView import HexaTableView
+ widget = HexaTableView(parent)
+ return widget
+
+ def clear(self):
+ self.getWidget().setArrayData(None)
+
+ def setData(self, data):
+ data = self.normalizeData(data)
+ widget = self.getWidget()
+ widget.setArrayData(data)
+
+ def axesNames(self, data, info):
+ return []
+
+ def getDataPriority(self, data, info):
+ if info.size <= 0:
+ return DataView.UNSUPPORTED
+ if info.isVoid:
+ return 2000
+ return DataView.UNSUPPORTED
+
+
class _Hdf5View(DataView):
"""View displaying data using text"""
@@ -727,7 +853,7 @@ class _Hdf5View(DataView):
widget.setData(data)
def axesNames(self, data, info):
- return []
+ return None
def getDataPriority(self, data, info):
widget = self.getWidget()
@@ -750,11 +876,28 @@ class _RawView(CompositeDataView):
modeId=RAW_MODE,
label="Raw",
icon=icons.getQIcon("view-raw"))
+ self.addView(_HexaView(parent))
self.addView(_ScalarView(parent))
self.addView(_ArrayView(parent))
self.addView(_RecordView(parent))
+class _ImageView(CompositeDataView):
+ """View displaying data as 2D image
+
+ It choose between Plot2D and ComplexImageView widgets
+ """
+
+ def __init__(self, parent):
+ super(_ImageView, self).__init__(
+ parent=parent,
+ modeId=PLOT2D_MODE,
+ label="Image",
+ icon=icons.getQIcon("view-2d"))
+ self.addView(_ComplexImageView(parent))
+ self.addView(_Plot2dView(parent))
+
+
class _NXdataScalarView(DataView):
"""DataView using a table view for displaying NXdata scalars:
0-D signal or n-D signal with *@interpretation=scalar*"""
@@ -806,7 +949,7 @@ class _NXdataCurveView(DataView):
def axesNames(self, data, info):
# disabled (used by default axis selector widget in Hdf5Viewer)
- return []
+ return None
def clear(self):
self.getWidget().clear()
@@ -814,10 +957,10 @@ class _NXdataCurveView(DataView):
def setData(self, data):
data = self.normalizeData(data)
nxd = NXdata(data)
- signal_name = data.attrs["signal"]
+ signal_name = get_attr_as_string(data, "signal")
group_name = data.name
- if nxd.axes_names[-1] is not None:
- x_errors = nxd.get_axis_errors(nxd.axes_names[-1])
+ if nxd.axes_dataset_names[-1] is not None:
+ x_errors = nxd.get_axis_errors(nxd.axes_dataset_names[-1])
else:
x_errors = None
@@ -853,7 +996,7 @@ class _NXdataXYVScatterView(DataView):
def axesNames(self, data, info):
# disabled (used by default axis selector widget in Hdf5Viewer)
- return []
+ return None
def clear(self):
self.getWidget().clear()
@@ -861,7 +1004,7 @@ class _NXdataXYVScatterView(DataView):
def setData(self, data):
data = self.normalizeData(data)
nxd = NXdata(data)
- signal_name = data.attrs["signal"]
+ signal_name = get_attr_as_string(data, "signal")
# signal_errors = nx.errors # not supported
group_name = data.name
x_axis, y_axis = nxd.axes[-2:]
@@ -902,7 +1045,8 @@ class _NXdataImageView(DataView):
return widget
def axesNames(self, data, info):
- return []
+ # disabled (used by default axis selector widget in Hdf5Viewer)
+ return None
def clear(self):
self.getWidget().clear()
@@ -910,7 +1054,7 @@ class _NXdataImageView(DataView):
def setData(self, data):
data = self.normalizeData(data)
nxd = NXdata(data)
- signal_name = data.attrs["signal"]
+ signal_name = get_attr_as_string(data, "signal")
group_name = data.name
y_axis, x_axis = nxd.axes[-2:]
y_label, x_label = nxd.axes_names[-2:]
@@ -942,7 +1086,8 @@ class _NXdataStackView(DataView):
return widget
def axesNames(self, data, info):
- return []
+ # disabled (used by default axis selector widget in Hdf5Viewer)
+ return None
def clear(self):
self.getWidget().clear()
@@ -950,7 +1095,7 @@ class _NXdataStackView(DataView):
def setData(self, data):
data = self.normalizeData(data)
nxd = NXdata(data)
- signal_name = data.attrs["signal"]
+ signal_name = get_attr_as_string(data, "signal")
group_name = data.name
z_axis, y_axis, x_axis = nxd.axes[-3:]
z_label, y_label, x_label = nxd.axes_names[-3:]
diff --git a/silx/gui/data/Hdf5TableView.py b/silx/gui/data/Hdf5TableView.py
index 5d79907..ba737e3 100644
--- a/silx/gui/data/Hdf5TableView.py
+++ b/silx/gui/data/Hdf5TableView.py
@@ -30,7 +30,7 @@ from __future__ import division
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "07/04/2017"
+__date__ = "29/09/2017"
import functools
import os.path
@@ -40,6 +40,13 @@ import silx.io
from .TextFormatter import TextFormatter
import silx.gui.hdf5
from silx.gui.widgets import HierarchicalTableView
+from ..hdf5.Hdf5Formatter import Hdf5Formatter
+
+try:
+ import h5py
+except ImportError:
+ h5py = None
+
_logger = logging.getLogger(__name__)
@@ -177,6 +184,7 @@ class Hdf5TableModel(HierarchicalTableView.HierarchicalTableModel):
self.__obj = None
self.__data = _TableData(columnCount=4)
self.__formatter = None
+ self.__hdf5Formatter = Hdf5Formatter(self)
formatter = TextFormatter(self)
self.setFormatter(formatter)
self.setObject(data)
@@ -207,7 +215,7 @@ class Hdf5TableModel(HierarchicalTableView.HierarchicalTableModel):
value = cell.value()
if callable(value):
value = value(self.__obj)
- return str(value)
+ return value
return None
def flags(self, index):
@@ -248,6 +256,22 @@ class Hdf5TableModel(HierarchicalTableView.HierarchicalTableModel):
else:
self.reset()
+ def __formatHdf5Type(self, dataset):
+ """Format the HDF5 type"""
+ return self.__hdf5Formatter.humanReadableHdf5Type(dataset)
+
+ def __formatDType(self, dataset):
+ """Format the numpy dtype"""
+ return self.__hdf5Formatter.humanReadableType(dataset, full=True)
+
+ def __formatShape(self, dataset):
+ """Format the shape"""
+ if dataset.shape is None or len(dataset.shape) <= 1:
+ return self.__hdf5Formatter.humanReadableShape(dataset)
+ size = dataset.size
+ shape = self.__hdf5Formatter.humanReadableShape(dataset)
+ return u"%s = %s" % (shape, size)
+
def __initProperties(self):
"""Initialize the list of available properties according to the defined
h5py-like object."""
@@ -270,26 +294,48 @@ class Hdf5TableModel(HierarchicalTableView.HierarchicalTableModel):
else:
objectType = obj.__class__.__name__
self.__data.addHeaderRow(headerLabel="HDF5 %s" % objectType)
- self.__data.addHeaderRow(headerLabel="Path info")
- self.__data.addHeaderValueRow("basename", lambda x: os.path.basename(x.name))
- self.__data.addHeaderValueRow("name", lambda x: x.name)
- if silx.io.is_file(obj):
- self.__data.addHeaderValueRow("filename", lambda x: x.filename)
+ SEPARATOR = "::"
+ self.__data.addHeaderRow(headerLabel="Path info")
if isinstance(obj, silx.gui.hdf5.H5Node):
# helpful informations if the object come from an HDF5 tree
- self.__data.addHeaderValueRow("local_basename", lambda x: x.local_basename)
- self.__data.addHeaderValueRow("local_name", lambda x: x.local_name)
- self.__data.addHeaderValueRow("local_filename", lambda x: x.local_file.filename)
+ self.__data.addHeaderValueRow("Basename", lambda x: x.local_basename)
+ self.__data.addHeaderValueRow("Name", lambda x: x.local_name)
+ local = lambda x: x.local_filename + SEPARATOR + x.local_name
+ self.__data.addHeaderValueRow("Local", local)
+ physical = lambda x: x.physical_filename + SEPARATOR + x.physical_name
+ self.__data.addHeaderValueRow("Physical", physical)
+ else:
+ # it's a real H5py object
+ self.__data.addHeaderValueRow("Basename", lambda x: os.path.basename(x.name))
+ self.__data.addHeaderValueRow("Name", lambda x: x.name)
+ self.__data.addHeaderValueRow("File", lambda x: x.file.filename)
+
+ if hasattr(obj, "path"):
+ # That's a link
+ if hasattr(obj, "filename"):
+ link = lambda x: x.filename + SEPARATOR + x.path
+ else:
+ link = lambda x: x.path
+ self.__data.addHeaderValueRow("Link", link)
+ else:
+ if silx.io.is_file(obj):
+ physical = lambda x: x.filename + SEPARATOR + x.name
+ else:
+ physical = lambda x: x.file.filename + SEPARATOR + x.name
+ self.__data.addHeaderValueRow("Physical", physical)
if hasattr(obj, "dtype"):
+
self.__data.addHeaderRow(headerLabel="Data info")
- self.__data.addHeaderValueRow("dtype", lambda x: x.dtype)
+
+ if h5py is not None and hasattr(obj, "id"):
+ # display the HDF5 type
+ self.__data.addHeaderValueRow("HDF5 type", self.__formatHdf5Type)
+ self.__data.addHeaderValueRow("dtype", self.__formatDType)
if hasattr(obj, "shape"):
- self.__data.addHeaderValueRow("shape", lambda x: x.shape)
- if hasattr(obj, "size"):
- self.__data.addHeaderValueRow("size", lambda x: x.size)
+ self.__data.addHeaderValueRow("shape", self.__formatShape)
if hasattr(obj, "chunks") and obj.chunks is not None:
self.__data.addHeaderValueRow("chunks", lambda x: x.chunks)
@@ -354,6 +400,8 @@ class Hdf5TableModel(HierarchicalTableView.HierarchicalTableModel):
if formatter is self.__formatter:
return
+ self.__hdf5Formatter.setTextFormatter(formatter)
+
if qt.qVersion() > "4.6":
self.beginResetModel()
diff --git a/silx/gui/data/HexaTableView.py b/silx/gui/data/HexaTableView.py
new file mode 100644
index 0000000..1b2a7e9
--- /dev/null
+++ b/silx/gui/data/HexaTableView.py
@@ -0,0 +1,278 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2017 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 defines model and widget to display raw data using an
+hexadecimal viewer.
+"""
+from __future__ import division
+
+import numpy
+import collections
+from silx.gui import qt
+import silx.io.utils
+from silx.third_party import six
+from silx.gui.widgets.TableWidget import CopySelectedCellsAction
+
+__authors__ = ["V. Valls"]
+__license__ = "MIT"
+__date__ = "27/09/2017"
+
+
+class _VoidConnector(object):
+ """Byte connector to a numpy.void data.
+
+ It uses a cache of 32 x 1KB and a direct read access API from HDF5.
+ """
+
+ def __init__(self, data):
+ self.__cache = collections.OrderedDict()
+ self.__len = data.itemsize
+ self.__data = data
+
+ def __getBuffer(self, bufferId):
+ if bufferId not in self.__cache:
+ pos = bufferId << 10
+ data = self.__data.tobytes()[pos:pos + 1024]
+ self.__cache[bufferId] = data
+ if len(self.__cache) > 32:
+ self.__cache.popitem()
+ else:
+ data = self.__cache[bufferId]
+ return data
+
+ def __getitem__(self, pos):
+ """Returns the value of the byte at the given position.
+
+ :param uint pos: Position of the byte
+ :rtype: int
+ """
+ bufferId = pos >> 10
+ bufferPos = pos & 0b1111111111
+ data = self.__getBuffer(bufferId)
+ value = data[bufferPos]
+ if six.PY2:
+ return ord(value)
+ else:
+ return value
+
+ def __len__(self):
+ """
+ Returns the number of available bytes.
+
+ :rtype: uint
+ """
+ return self.__len
+
+
+class HexaTableModel(qt.QAbstractTableModel):
+ """This data model provides access to a numpy void data.
+
+ Bytes are displayed one by one as a hexadecimal viewer.
+
+ The 16th first columns display bytes as hexadecimal, the last column
+ displays the same data as ASCII.
+
+ :param qt.QObject parent: Parent object
+ :param data: A numpy array or a h5py dataset
+ """
+ def __init__(self, parent=None, data=None):
+ qt.QAbstractTableModel.__init__(self, parent)
+
+ self.__data = None
+ self.__connector = None
+ self.setArrayData(data)
+
+ if hasattr(qt.QFontDatabase, "systemFont"):
+ self.__font = qt.QFontDatabase.systemFont(qt.QFontDatabase.FixedFont)
+ else:
+ self.__font = qt.QFont("Monospace")
+ self.__font.setStyleHint(qt.QFont.TypeWriter)
+ self.__palette = qt.QPalette()
+
+ def rowCount(self, parent_idx=None):
+ """Returns number of rows to be displayed in table"""
+ if self.__connector is None:
+ return 0
+ return ((len(self.__connector) - 1) >> 4) + 1
+
+ def columnCount(self, parent_idx=None):
+ """Returns number of columns to be displayed in table"""
+ return 0x10 + 1
+
+ def data(self, index, role=qt.Qt.DisplayRole):
+ """QAbstractTableModel method to access data values
+ in the format ready to be displayed"""
+ if not index.isValid():
+ return None
+
+ if self.__connector is None:
+ return None
+
+ row = index.row()
+ column = index.column()
+
+ if role == qt.Qt.DisplayRole:
+ if column == 0x10:
+ start = (row << 4)
+ text = ""
+ for i in range(0x10):
+ pos = start + i
+ if pos >= len(self.__connector):
+ break
+ value = self.__connector[pos]
+ if value > 0x20 and value < 0x7F:
+ text += chr(value)
+ else:
+ text += "."
+ return text
+ else:
+ pos = (row << 4) + column
+ if pos < len(self.__connector):
+ value = self.__connector[pos]
+ return "%02X" % value
+ else:
+ return ""
+ elif role == qt.Qt.FontRole:
+ return self.__font
+
+ elif role == qt.Qt.BackgroundColorRole:
+ pos = (row << 4) + column
+ if column != 0x10 and pos >= len(self.__connector):
+ return self.__palette.color(qt.QPalette.Disabled, qt.QPalette.Background)
+ else:
+ return None
+
+ return None
+
+ def headerData(self, section, orientation, role=qt.Qt.DisplayRole):
+ """Returns the 0-based row or column index, for display in the
+ horizontal and vertical headers"""
+ if section == -1:
+ # PyQt4 send -1 when there is columns but no rows
+ return None
+
+ if role == qt.Qt.DisplayRole:
+ if orientation == qt.Qt.Vertical:
+ return "%02X" % (section << 4)
+ if orientation == qt.Qt.Horizontal:
+ if section == 0x10:
+ return "ASCII"
+ else:
+ return "%02X" % section
+ elif role == qt.Qt.FontRole:
+ return self.__font
+ elif role == qt.Qt.TextAlignmentRole:
+ if orientation == qt.Qt.Vertical:
+ return qt.Qt.AlignRight
+ if orientation == qt.Qt.Horizontal:
+ if section == 0x10:
+ return qt.Qt.AlignLeft
+ else:
+ return qt.Qt.AlignCenter
+ return None
+
+ def flags(self, index):
+ """QAbstractTableModel method to inform the view whether data
+ is editable or not.
+ """
+ row = index.row()
+ column = index.column()
+ pos = (row << 4) + column
+ if column != 0x10 and pos >= len(self.__connector):
+ return qt.Qt.NoItemFlags
+ return qt.QAbstractTableModel.flags(self, index)
+
+ def setArrayData(self, data):
+ """Set the data array.
+
+ :param data: A numpy object or a dataset.
+ """
+ if qt.qVersion() > "4.6":
+ self.beginResetModel()
+
+ self.__connector = None
+ self.__data = data
+ if self.__data is not None:
+ if silx.io.utils.is_dataset(self.__data):
+ data = data[()]
+ elif isinstance(self.__data, numpy.ndarray):
+ data = data[()]
+ self.__connector = _VoidConnector(data)
+
+ if qt.qVersion() > "4.6":
+ self.endResetModel()
+ else:
+ self.reset()
+
+ def arrayData(self):
+ """Returns the internal data.
+
+ :rtype: numpy.ndarray of h5py.Dataset
+ """
+ return self.__data
+
+
+class HexaTableView(qt.QTableView):
+ """TableView using HexaTableModel as default model.
+
+ It customs the column size to provide a better layout.
+ """
+ def __init__(self, parent=None):
+ """
+ Constructor
+
+ :param qt.QWidget parent: parent QWidget
+ """
+ qt.QTableView.__init__(self, parent)
+
+ model = HexaTableModel(self)
+ self.setModel(model)
+ self._copyAction = CopySelectedCellsAction(self)
+ self.addAction(self._copyAction)
+
+ def copy(self):
+ self._copyAction.trigger()
+
+ def setArrayData(self, data):
+ """Set the data array.
+
+ :param data: A numpy object or a dataset.
+ """
+ self.model().setArrayData(data)
+ self.__fixHeader()
+
+ def __fixHeader(self):
+ """Update the view according to the state of the auto-resize"""
+ header = self.horizontalHeader()
+ if qt.qVersion() < "5.0":
+ setResizeMode = header.setResizeMode
+ else:
+ setResizeMode = header.setSectionResizeMode
+
+ header.setDefaultSectionSize(30)
+ header.setStretchLastSection(True)
+ for i in range(0x10):
+ setResizeMode(i, qt.QHeaderView.Fixed)
+ setResizeMode(0x10, qt.QHeaderView.Stretch)
diff --git a/silx/gui/data/NXdataWidgets.py b/silx/gui/data/NXdataWidgets.py
index 343c7f9..b820380 100644
--- a/silx/gui/data/NXdataWidgets.py
+++ b/silx/gui/data/NXdataWidgets.py
@@ -26,7 +26,7 @@
"""
__authors__ = ["P. Knobel"]
__license__ = "MIT"
-__date__ = "20/03/2017"
+__date__ = "27/06/2017"
import numpy
@@ -135,8 +135,8 @@ class ArrayCurvePlot(qt.QWidget):
self.selectorDock.show()
self._plot.setGraphTitle(title or "")
- self._plot.setGraphXLabel(self.__axis_name or "X")
- self._plot.setGraphYLabel(self.__signal_name or "Y")
+ self._plot.getXAxis().setLabel(self.__axis_name or "X")
+ self._plot.getYAxis().setLabel(self.__signal_name or "Y")
self._updateCurve()
if not self.__selector_is_connected:
@@ -188,8 +188,8 @@ class ArrayCurvePlot(qt.QWidget):
xerror=self.__axis_errors,
yerror=y_errors)
self._plot.resetZoom()
- self._plot.setGraphXLabel(self.__axis_name)
- self._plot.setGraphYLabel(self.__signal_name)
+ self._plot.getXAxis().setLabel(self.__axis_name)
+ self._plot.getYAxis().setLabel(self.__signal_name)
def clear(self):
self._plot.clear()
@@ -289,8 +289,8 @@ class ArrayImagePlot(qt.QWidget):
self.selectorDock.show()
self._plot.setGraphTitle(title or "")
- self._plot.setGraphXLabel(self.__x_axis_name or "X")
- self._plot.setGraphYLabel(self.__y_axis_name or "Y")
+ self._plot.getXAxis().setLabel(self.__x_axis_name or "X")
+ self._plot.getYAxis().setLabel(self.__y_axis_name or "Y")
self._updateImage()
@@ -352,8 +352,8 @@ class ArrayImagePlot(qt.QWidget):
numpy.ravel(scattery),
numpy.ravel(img),
legend=legend)
- self._plot.setGraphXLabel(self.__x_axis_name)
- self._plot.setGraphYLabel(self.__y_axis_name)
+ self._plot.getXAxis().setLabel(self.__x_axis_name)
+ self._plot.getYAxis().setLabel(self.__y_axis_name)
self._plot.resetZoom()
def clear(self):
@@ -450,8 +450,8 @@ class ArrayStackPlot(qt.QWidget):
self._stack_view.setGraphTitle(title or "")
# by default, the z axis is the image position (dimension not plotted)
- self._stack_view.setGraphXLabel(self.__x_axis_name or "X")
- self._stack_view.setGraphYLabel(self.__y_axis_name or "Y")
+ self._stack_view.getPlot().getXAxis().setLabel(self.__x_axis_name or "X")
+ self._stack_view.getPlot().getYAxis().setLabel(self.__y_axis_name or "Y")
self._updateStack()
diff --git a/silx/gui/data/RecordTableView.py b/silx/gui/data/RecordTableView.py
index ce6a178..54881b7 100644
--- a/silx/gui/data/RecordTableView.py
+++ b/silx/gui/data/RecordTableView.py
@@ -37,7 +37,7 @@ from silx.gui.widgets.TableWidget import CopySelectedCellsAction
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "27/01/2017"
+__date__ = "02/10/2017"
class _MultiLineItem(qt.QItemDelegate):
@@ -206,9 +206,9 @@ class RecordTableModel(qt.QAbstractTableModel):
data = data[key[1]]
if role == qt.Qt.DisplayRole:
- return self.__formatter.toString(data)
+ return self.__formatter.toString(data, dtype=self.__data.dtype)
elif role == qt.Qt.EditRole:
- return self.__editFormatter.toString(data)
+ return self.__editFormatter.toString(data, dtype=self.__data.dtype)
return None
def headerData(self, section, orientation, role=qt.Qt.DisplayRole):
@@ -270,11 +270,11 @@ class RecordTableModel(qt.QAbstractTableModel):
else:
self.__is_array = False
-
self.__fields = []
if data is not None:
if data.dtype.fields is not None:
- for name, (dtype, _index) in data.dtype.fields.items():
+ fields = sorted(data.dtype.fields.items(), key=lambda e: e[1][1])
+ for name, (dtype, _index) in fields:
if dtype.shape != tuple():
keys = itertools.product(*[range(x) for x in dtype.shape])
for key in keys:
diff --git a/silx/gui/data/TextFormatter.py b/silx/gui/data/TextFormatter.py
index f074de5..37e1f48 100644
--- a/silx/gui/data/TextFormatter.py
+++ b/silx/gui/data/TextFormatter.py
@@ -27,14 +27,18 @@ data module to format data as text in the same way."""
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "26/04/2017"
+__date__ = "27/09/2017"
import numpy
import numbers
-import binascii
from silx.third_party import six
from silx.gui import qt
+try:
+ import h5py
+except ImportError:
+ h5py = None
+
class TextFormatter(qt.QObject):
"""Formatter to convert data to string.
@@ -73,11 +77,13 @@ class TextFormatter(qt.QObject):
self.__floatFormat = formatter.floatFormat()
self.__useQuoteForText = formatter.useQuoteForText()
self.__imaginaryUnit = formatter.imaginaryUnit()
+ self.__enumFormat = formatter.enumFormat()
else:
self.__integerFormat = "%d"
self.__floatFormat = "%g"
self.__useQuoteForText = True
self.__imaginaryUnit = u"j"
+ self.__enumFormat = u"%(name)s(%(value)d)"
def integerFormat(self):
"""Returns the format string controlling how the integer data
@@ -162,40 +168,151 @@ class TextFormatter(qt.QObject):
self.__imaginaryUnit = imaginaryUnit
self.formatChanged.emit()
- def toString(self, data):
+ def setEnumFormat(self, value):
+ """Set format string controlling how the enum data are
+ formated by this object.
+
+ :param str value: Format string (e.g. "%(name)s(%(value)d)").
+ This is the C-style format string used by python when formatting
+ strings with the modulus operator.
+ """
+ if self.__enumFormat == value:
+ return
+ self.__enumFormat = value
+ self.formatChanged.emit()
+
+ def enumFormat(self):
+ """Returns the format string controlling how the enum data
+ are formated by this object.
+
+ This is the C-style format string used by python when formatting
+ strings with the modulus operator.
+
+ :rtype: str
+ """
+ return self.__enumFormat
+
+ def __formatText(self, text):
+ if self.__useQuoteForText:
+ text = "\"%s\"" % text.replace("\\", "\\\\").replace("\"", "\\\"")
+ return text
+
+ def __formatBinary(self, data):
+ if isinstance(data, numpy.void):
+ if six.PY2:
+ data = [ord(d) for d in data.item()]
+ else:
+ data = data.item().astype(numpy.uint8)
+ else:
+ data = [ord(d) for d in data]
+ data = ["\\x%02X" % d for d in data]
+ if self.__useQuoteForText:
+ return "b\"%s\"" % "".join(data)
+ else:
+ return "".join(data)
+
+ def __formatSafeAscii(self, data):
+ if six.PY2:
+ data = [ord(d) for d in data]
+ data = [chr(d) if (d > 0x20 and d < 0x7F) else "\\x%02X" % d for d in data]
+ if self.__useQuoteForText:
+ data = [c if c != '"' else "\\" + c for c in data]
+ return "b\"%s\"" % "".join(data)
+ else:
+ return "".join(data)
+
+ def __formatH5pyObject(self, data, dtype):
+ # That's an HDF5 object
+ ref = h5py.check_dtype(ref=dtype)
+ if ref is not None:
+ if bool(data):
+ return "REF"
+ else:
+ return "NULL_REF"
+ vlen = h5py.check_dtype(vlen=dtype)
+ if vlen is not None:
+ if vlen == six.text_type:
+ # HDF5 UTF8
+ return self.__formatText(data)
+ elif vlen == six.binary_type:
+ # HDF5 ASCII
+ try:
+ text = "%s" % data.decode("ascii")
+ return self.__formatText(text)
+ except UnicodeDecodeError:
+ return self.__formatSafeAscii(data)
+ return None
+
+ def toString(self, data, dtype=None):
"""Format a data into a string using formatter options
:param object data: Data to render
+ :param dtype: enforce a dtype (mostly used to remember the h5py dtype,
+ special h5py dtypes are not propagated from array to items)
:rtype: str
"""
if isinstance(data, tuple):
text = [self.toString(d) for d in data]
return "(" + " ".join(text) + ")"
- elif isinstance(data, (list, numpy.ndarray)):
+ elif isinstance(data, list):
text = [self.toString(d) for d in data]
return "[" + " ".join(text) + "]"
+ elif isinstance(data, (numpy.ndarray)):
+ if dtype is None:
+ dtype = data.dtype
+ if data.shape == ():
+ # it is a scaler
+ return self.toString(data[()], dtype)
+ else:
+ text = [self.toString(d, dtype) for d in data]
+ return "[" + " ".join(text) + "]"
elif isinstance(data, numpy.void):
- dtype = data.dtype
+ if dtype is None:
+ dtype = data.dtype
if data.dtype.fields is not None:
- text = [self.toString(data[f]) for f in dtype.fields]
+ text = [self.toString(data[f], dtype) for f in dtype.fields]
return "(" + " ".join(text) + ")"
- return "0x" + binascii.hexlify(data).decode("ascii")
- elif isinstance(data, (numpy.string_, numpy.object_, bytes)):
- # This have to be done before checking python string inheritance
+ return self.__formatBinary(data)
+ elif isinstance(data, (numpy.unicode_, six.text_type)):
+ return self.__formatText(data)
+ elif isinstance(data, (numpy.string_, six.binary_type)):
+ if dtype is not None:
+ # Maybe a sub item from HDF5
+ if dtype.kind == 'S':
+ try:
+ text = "%s" % data.decode("ascii")
+ return self.__formatText(text)
+ except UnicodeDecodeError:
+ return self.__formatSafeAscii(data)
+ elif dtype.kind == 'O':
+ if h5py is not None:
+ text = self.__formatH5pyObject(data, dtype)
+ if text is not None:
+ return text
try:
+ # Try ascii/utf-8
text = "%s" % data.decode("utf-8")
- if self.__useQuoteForText:
- text = "\"%s\"" % text.replace("\"", "\\\"")
- return text
+ return self.__formatText(text)
except UnicodeDecodeError:
pass
- return "0x" + binascii.hexlify(data).decode("ascii")
+ return self.__formatBinary(data)
elif isinstance(data, six.string_types):
text = "%s" % data
- if self.__useQuoteForText:
- text = "\"%s\"" % text.replace("\"", "\\\"")
- return text
- elif isinstance(data, (numpy.integer, numbers.Integral)):
+ return self.__formatText(text)
+ elif isinstance(data, (numpy.integer)):
+ if dtype is None:
+ dtype = data.dtype
+ if h5py is not None:
+ enumType = h5py.check_dtype(enum=dtype)
+ if enumType is not None:
+ for key, value in enumType.items():
+ if value == data:
+ result = {}
+ result["name"] = key
+ result["value"] = data
+ return self.__enumFormat % result
+ return self.__integerFormat % data
+ elif isinstance(data, (numbers.Integral)):
return self.__integerFormat % data
elif isinstance(data, (numbers.Real, numpy.floating)):
# It have to be done before complex checking
@@ -219,4 +336,21 @@ class TextFormatter(qt.QObject):
template = self.__floatFormat
params = (data.real)
return template % params
+ elif h5py is not None and isinstance(data, h5py.h5r.Reference):
+ dtype = h5py.special_dtype(ref=h5py.Reference)
+ text = self.__formatH5pyObject(data, dtype)
+ return text
+ elif h5py is not None and isinstance(data, h5py.h5r.RegionReference):
+ dtype = h5py.special_dtype(ref=h5py.RegionReference)
+ text = self.__formatH5pyObject(data, dtype)
+ return text
+ elif isinstance(data, numpy.object_) or dtype is not None:
+ if dtype is None:
+ dtype = data.dtype
+ if h5py is not None:
+ text = self.__formatH5pyObject(data, dtype)
+ if text is not None:
+ return text
+ # That's a numpy object
+ return str(data)
return str(data)
diff --git a/silx/gui/data/test/test_dataviewer.py b/silx/gui/data/test/test_dataviewer.py
index 5a0de0b..dd3114a 100644
--- a/silx/gui/data/test/test_dataviewer.py
+++ b/silx/gui/data/test/test_dataviewer.py
@@ -24,7 +24,7 @@
# ###########################################################################*/
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "10/04/2017"
+__date__ = "22/08/2017"
import os
import tempfile
@@ -42,8 +42,6 @@ from silx.gui.data.DataViewerFrame import DataViewerFrame
from silx.gui.test.utils import SignalListener
from silx.gui.test.utils import TestCaseQt
-from silx.gui.hdf5.test import _mock
-
try:
import h5py
except ImportError:
@@ -111,6 +109,24 @@ class AbstractDataViewerTests(TestCaseQt):
self.assertEqual(DataViewer.RAW_MODE, widget.displayMode())
self.assertIn(DataViewer.PLOT2D_MODE, availableModes)
+ def test_plot_2d_bool(self):
+ data = numpy.zeros((10, 10), dtype=numpy.bool)
+ data[::2, ::2] = True
+ widget = self.create_widget()
+ widget.setData(data)
+ availableModes = set([v.modeId() for v in widget.currentAvailableViews()])
+ self.assertEqual(DataViewer.RAW_MODE, widget.displayMode())
+ self.assertIn(DataViewer.PLOT2D_MODE, availableModes)
+
+ def test_plot_2d_complex_data(self):
+ data = numpy.arange(3 ** 2, dtype=numpy.complex)
+ data.shape = [3] * 2
+ widget = self.create_widget()
+ widget.setData(data)
+ availableModes = set([v.modeId() for v in widget.currentAvailableViews()])
+ self.assertEqual(DataViewer.RAW_MODE, widget.displayMode())
+ self.assertIn(DataViewer.PLOT2D_MODE, availableModes)
+
def test_plot_3d_data(self):
data = numpy.arange(3 ** 3)
data.shape = [3] * 3
@@ -212,6 +228,7 @@ class AbstractDataViewerTests(TestCaseQt):
self.assertTrue(view not in widget.availableViews())
self.assertTrue(view not in widget.currentAvailableViews())
+
class TestDataViewer(AbstractDataViewerTests):
def create_widget(self):
return DataViewer()
@@ -225,11 +242,10 @@ class TestDataViewerFrame(AbstractDataViewerTests):
class TestDataView(TestCaseQt):
def createComplexData(self):
- line = [1, 2j, 3+3j, 4]
+ line = [1, 2j, 3 + 3j, 4]
image = [line, line, line, line]
cube = [image, image, image, image]
- data = numpy.array(cube,
- dtype=numpy.complex)
+ data = numpy.array(cube, dtype=numpy.complex)
return data
def createDataViewWithData(self, dataViewClass, data):
diff --git a/silx/gui/data/test/test_textformatter.py b/silx/gui/data/test/test_textformatter.py
index f21e033..2a7a66b 100644
--- a/silx/gui/data/test/test_textformatter.py
+++ b/silx/gui/data/test/test_textformatter.py
@@ -24,13 +24,22 @@
# ###########################################################################*/
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "24/01/2017"
+__date__ = "27/09/2017"
import unittest
+import shutil
+import tempfile
+import numpy
from silx.gui.test.utils import TestCaseQt
from silx.gui.test.utils import SignalListener
from ..TextFormatter import TextFormatter
+from silx.third_party import six
+
+try:
+ import h5py
+except ImportError:
+ h5py = None
class TestTextFormatter(TestCaseQt):
@@ -83,10 +92,108 @@ class TestTextFormatter(TestCaseQt):
self.assertEquals(result, '"toto"')
+class TestTextFormatterWithH5py(TestCaseQt):
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestTextFormatterWithH5py, cls).setUpClass()
+ if h5py is None:
+ raise unittest.SkipTest("h5py is not available")
+
+ cls.tmpDirectory = tempfile.mkdtemp()
+ cls.h5File = h5py.File("%s/formatter.h5" % cls.tmpDirectory, mode="w")
+ cls.formatter = TextFormatter()
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestTextFormatterWithH5py, cls).tearDownClass()
+ cls.h5File.close()
+ cls.h5File = None
+ shutil.rmtree(cls.tmpDirectory)
+
+ def create_dataset(self, data, dtype=None):
+ testName = "%s" % self.id()
+ dataset = self.h5File.create_dataset(testName, data=data, dtype=dtype)
+ return dataset
+
+ def testAscii(self):
+ d = self.create_dataset(data=b"abc")
+ result = self.formatter.toString(d[()], dtype=d.dtype)
+ self.assertEquals(result, '"abc"')
+
+ def testUnicode(self):
+ d = self.create_dataset(data=u"i\u2661cookies")
+ result = self.formatter.toString(d[()], dtype=d.dtype)
+ self.assertEquals(len(result), 11)
+ self.assertEquals(result, u'"i\u2661cookies"')
+
+ def testBadAscii(self):
+ d = self.create_dataset(data=b"\xF0\x9F\x92\x94")
+ result = self.formatter.toString(d[()], dtype=d.dtype)
+ self.assertEquals(result, 'b"\\xF0\\x9F\\x92\\x94"')
+
+ def testVoid(self):
+ d = self.create_dataset(data=numpy.void(b"abc\xF0"))
+ result = self.formatter.toString(d[()], dtype=d.dtype)
+ self.assertEquals(result, 'b"\\x61\\x62\\x63\\xF0"')
+
+ def testEnum(self):
+ dtype = h5py.special_dtype(enum=('i', {"RED": 0, "GREEN": 1, "BLUE": 42}))
+ d = numpy.array(42, dtype=dtype)
+ d = self.create_dataset(data=d)
+ result = self.formatter.toString(d[()], dtype=d.dtype)
+ self.assertEquals(result, 'BLUE(42)')
+
+ def testRef(self):
+ dtype = h5py.special_dtype(ref=h5py.Reference)
+ d = numpy.array(self.h5File.ref, dtype=dtype)
+ d = self.create_dataset(data=d)
+ result = self.formatter.toString(d[()], dtype=d.dtype)
+ self.assertEquals(result, 'REF')
+
+ def testArrayAscii(self):
+ d = self.create_dataset(data=[b"abc"])
+ result = self.formatter.toString(d[()], dtype=d.dtype)
+ self.assertEquals(result, '["abc"]')
+
+ def testArrayUnicode(self):
+ dtype = h5py.special_dtype(vlen=six.text_type)
+ d = numpy.array([u"i\u2661cookies"], dtype=dtype)
+ d = self.create_dataset(data=d)
+ result = self.formatter.toString(d[()], dtype=d.dtype)
+ self.assertEquals(len(result), 13)
+ self.assertEquals(result, u'["i\u2661cookies"]')
+
+ def testArrayBadAscii(self):
+ d = self.create_dataset(data=[b"\xF0\x9F\x92\x94"])
+ result = self.formatter.toString(d[()], dtype=d.dtype)
+ self.assertEquals(result, '[b"\\xF0\\x9F\\x92\\x94"]')
+
+ def testArrayVoid(self):
+ d = self.create_dataset(data=numpy.void([b"abc\xF0"]))
+ result = self.formatter.toString(d[()], dtype=d.dtype)
+ self.assertEquals(result, '[b"\\x61\\x62\\x63\\xF0"]')
+
+ def testArrayEnum(self):
+ dtype = h5py.special_dtype(enum=('i', {"RED": 0, "GREEN": 1, "BLUE": 42}))
+ d = numpy.array([42, 1, 100], dtype=dtype)
+ d = self.create_dataset(data=d)
+ result = self.formatter.toString(d[()], dtype=d.dtype)
+ self.assertEquals(result, '[BLUE(42) GREEN(1) 100]')
+
+ def testArrayRef(self):
+ dtype = h5py.special_dtype(ref=h5py.Reference)
+ d = numpy.array([self.h5File.ref, None], dtype=dtype)
+ d = self.create_dataset(data=d)
+ result = self.formatter.toString(d[()], dtype=d.dtype)
+ self.assertEquals(result, '[REF NULL_REF]')
+
+
def suite():
+ loadTests = unittest.defaultTestLoader.loadTestsFromTestCase
test_suite = unittest.TestSuite()
- test_suite.addTest(
- unittest.defaultTestLoader.loadTestsFromTestCase(TestTextFormatter))
+ test_suite.addTest(loadTests(TestTextFormatter))
+ test_suite.addTest(loadTests(TestTextFormatterWithH5py))
return test_suite
diff --git a/silx/gui/fit/BackgroundWidget.py b/silx/gui/fit/BackgroundWidget.py
index 577a8c7..2171e87 100644
--- a/silx/gui/fit/BackgroundWidget.py
+++ b/silx/gui/fit/BackgroundWidget.py
@@ -26,7 +26,11 @@
# #########################################################################*/
"""This module provides a background configuration widget
:class:`BackgroundWidget` and a corresponding dialog window
-:class:`BackgroundDialog`."""
+:class:`BackgroundDialog`.
+
+.. image:: img/BackgroundDialog.png
+ :height: 300px
+"""
import sys
import numpy
from silx.gui import qt
@@ -35,7 +39,7 @@ from silx.math.fit import filters
__authors__ = ["V.A. Sole", "P. Knobel"]
__license__ = "MIT"
-__date__ = "24/01/2017"
+__date__ = "28/06/2017"
class HorizontalSpacer(qt.QWidget):
@@ -262,7 +266,7 @@ class BackgroundParamWidget(qt.QWidget):
class BackgroundWidget(qt.QWidget):
- """Background configuration widget, with a :class:`PlotWindow`.
+ """Background configuration widget, with a plot to preview the results.
Strip and snip filters parameters can be adjusted using input widgets,
and the computed backgrounds are plotted next to the original data to
@@ -400,7 +404,7 @@ class BackgroundWidget(qt.QWidget):
legend='SNIP Background',
resetzoom=False)
if self._xmin is not None and self._xmax is not None:
- self.graphWidget.setGraphXLimits(xmin=self._xmin, xmax=self._xmax)
+ self.graphWidget.getXAxis().setLimits(self._xmin, self._xmax)
class BackgroundDialog(qt.QDialog):
@@ -467,11 +471,11 @@ class BackgroundDialog(qt.QDialog):
return self.parametersWidget.getParameters()
def setParameters(self, ddict):
- """See :meth:`BackgroundWidget.setParameters`"""
+ """See :meth:`BackgroundWidget.setPrintGeometry`"""
return self.parametersWidget.setParameters(ddict)
def setDefault(self, ddict):
- """Alias for :meth:`setParameters`"""
+ """Alias for :meth:`setPrintGeometry`"""
return self.setParameters(ddict)
diff --git a/silx/gui/fit/FitConfig.py b/silx/gui/fit/FitConfig.py
index 70b6fbe..04e411b 100644
--- a/silx/gui/fit/FitConfig.py
+++ b/silx/gui/fit/FitConfig.py
@@ -307,7 +307,7 @@ class SearchPage(qt.QWidget):
self.yScalingEntry.setToolTip(
"Data values will be multiplied by this value prior to peak" +
" search")
- self.yScalingEntry.setValidator(qt.QDoubleValidator())
+ self.yScalingEntry.setValidator(qt.QDoubleValidator(self))
layout3.addWidget(self.yScalingEntry)
# ----------------------------------------------------
@@ -324,7 +324,7 @@ class SearchPage(qt.QWidget):
"Peak search sensitivity threshold, expressed as a multiple " +
"of the standard deviation of the noise.\nMinimum value is 1 " +
"(to be detected, peak must be higher than the estimated noise)")
- sensivalidator = qt.QDoubleValidator()
+ sensivalidator = qt.QDoubleValidator(self)
sensivalidator.setBottom(1.0)
self.sensitivityEntry.setValidator(sensivalidator)
layout4.addWidget(self.sensitivityEntry)
@@ -418,7 +418,7 @@ class BackgroundPage(qt.QGroupBox):
"Factor used by the strip algorithm to decide whether a sample" +
"value should be stripped.\nThe value must be higher than the " +
"average of the 2 samples at +- w times this factor.\n")
- self.thresholdFactorEntry.setValidator(qt.QDoubleValidator())
+ self.thresholdFactorEntry.setValidator(qt.QDoubleValidator(self))
layout.addWidget(self.thresholdFactorEntry, 2, 1)
self.smoothStripGB = qt.QGroupBox("Apply smoothing prior to strip", self)
diff --git a/silx/gui/fit/FitWidget.py b/silx/gui/fit/FitWidget.py
index a5c3cfd..7012b63 100644
--- a/silx/gui/fit/FitWidget.py
+++ b/silx/gui/fit/FitWidget.py
@@ -87,6 +87,8 @@ class FitWidget(qt.QWidget):
run the estimation, set constraints on parameters and run the actual fit.
The results are displayed in a table.
+
+ .. image:: img/FitWidget.png
"""
sigFitWidgetSignal = qt.Signal(object)
"""This signal is emitted by the estimation and fit methods.
diff --git a/silx/gui/hdf5/Hdf5Formatter.py b/silx/gui/hdf5/Hdf5Formatter.py
new file mode 100644
index 0000000..3a4c1c1
--- /dev/null
+++ b/silx/gui/hdf5/Hdf5Formatter.py
@@ -0,0 +1,229 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2017 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 package provides a class sharred by widgets to format HDF5 data as
+text."""
+
+__authors__ = ["V. Valls"]
+__license__ = "MIT"
+__date__ = "27/09/2017"
+
+import numpy
+from silx.third_party import six
+from silx.gui import qt
+from silx.gui.data.TextFormatter import TextFormatter
+
+try:
+ import h5py
+except ImportError:
+ h5py = None
+
+
+class Hdf5Formatter(qt.QObject):
+ """Formatter to convert HDF5 data to string.
+ """
+
+ formatChanged = qt.Signal()
+ """Emitted when properties of the formatter change."""
+
+ def __init__(self, parent=None, textFormatter=None):
+ """
+ Constructor
+
+ :param qt.QObject parent: Owner of the object
+ :param TextFormatter formatter: Text formatter
+ """
+ qt.QObject.__init__(self, parent)
+ if textFormatter is not None:
+ self.__formatter = textFormatter
+ else:
+ self.__formatter = TextFormatter(self)
+ self.__formatter.formatChanged.connect(self.__formatChanged)
+
+ def textFormatter(self):
+ """Returns the used text formatter
+
+ :rtype: TextFormatter
+ """
+ return self.__formatter
+
+ def setTextFormatter(self, textFormatter):
+ """Set the text formatter to be used
+
+ :param TextFormatter textFormatter: The text formatter to use
+ """
+ if textFormatter is None:
+ raise ValueError("Formatter expected but None found")
+ if self.__formatter is textFormatter:
+ return
+ self.__formatter.formatChanged.disconnect(self.__formatChanged)
+ self.__formatter = textFormatter
+ self.__formatter.formatChanged.connect(self.__formatChanged)
+ self.__formatChanged()
+
+ def __formatChanged(self):
+ self.formatChanged.emit()
+
+ def humanReadableShape(self, dataset):
+ if dataset.shape is None:
+ return "none"
+ if dataset.shape == tuple():
+ return "scalar"
+ shape = [str(i) for i in dataset.shape]
+ text = u" \u00D7 ".join(shape)
+ return text
+
+ def humanReadableValue(self, dataset):
+ if dataset.shape is None:
+ return "No data"
+
+ dtype = dataset.dtype
+ if dataset.dtype.type == numpy.void:
+ if dtype.fields is None:
+ return "Raw data"
+
+ if dataset.shape == tuple():
+ numpy_object = dataset[()]
+ text = self.__formatter.toString(numpy_object, dtype=dataset.dtype)
+ else:
+ if dataset.size < 5 and dataset.compression is None:
+ numpy_object = dataset[0:5]
+ text = self.__formatter.toString(numpy_object, dtype=dataset.dtype)
+ else:
+ dimension = len(dataset.shape)
+ if dataset.compression is not None:
+ text = "Compressed %dD data" % dimension
+ else:
+ text = "%dD data" % dimension
+ return text
+
+ def humanReadableType(self, dataset, full=False):
+ dtype = dataset.dtype
+ return self.humanReadableDType(dtype, full)
+
+ def humanReadableDType(self, dtype, full=False):
+ if dtype == six.binary_type or numpy.issubdtype(dtype, numpy.string_):
+ text = "string"
+ if full:
+ text = "ASCII " + text
+ return text
+ elif dtype == six.text_type or numpy.issubdtype(dtype, numpy.unicode_):
+ text = "string"
+ if full:
+ text = "UTF-8 " + text
+ return text
+ elif dtype.type == numpy.object_:
+ ref = h5py.check_dtype(ref=dtype)
+ if ref is not None:
+ return "reference"
+ vlen = h5py.check_dtype(vlen=dtype)
+ if vlen is not None:
+ text = self.humanReadableDType(vlen, full=full)
+ if full:
+ text = "variable-length " + text
+ return text
+ return "object"
+ elif dtype.type == numpy.bool_:
+ return "bool"
+ elif dtype.type == numpy.void:
+ if dtype.fields is None:
+ return "opaque"
+ else:
+ if not full:
+ return "compound"
+ else:
+ compound = [d[0] for d in dtype.fields.values()]
+ compound = [self.humanReadableDType(d) for d in compound]
+ return "compound(%s)" % ", ".join(compound)
+ elif numpy.issubdtype(dtype, numpy.integer):
+ if h5py is not None:
+ enumType = h5py.check_dtype(enum=dtype)
+ if enumType is not None:
+ return "enum"
+
+ text = str(dtype.newbyteorder('N'))
+ if full:
+ if dtype.byteorder == "<":
+ text = "Little-endian " + text
+ elif dtype.byteorder == ">":
+ text = "Big-endian " + text
+ elif dtype.byteorder == "=":
+ text = "Native " + text
+
+ dtype = dtype.newbyteorder('N')
+ return text
+
+ def humanReadableHdf5Type(self, dataset):
+ """Format the internal HDF5 type as a string"""
+ t = dataset.id.get_type()
+ class_ = t.get_class()
+ if class_ == h5py.h5t.NO_CLASS:
+ return "NO_CLASS"
+ elif class_ == h5py.h5t.INTEGER:
+ return "INTEGER"
+ elif class_ == h5py.h5t.FLOAT:
+ return "FLOAT"
+ elif class_ == h5py.h5t.TIME:
+ return "TIME"
+ elif class_ == h5py.h5t.STRING:
+ charset = t.get_cset()
+ strpad = t.get_strpad()
+ text = ""
+
+ if strpad == h5py.h5t.STR_NULLTERM:
+ text += "NULLTERM"
+ elif strpad == h5py.h5t.STR_NULLPAD:
+ text += "NULLPAD"
+ elif strpad == h5py.h5t.STR_SPACEPAD:
+ text += "SPACEPAD"
+ else:
+ text += "UNKNOWN_STRPAD"
+
+ if t.is_variable_str():
+ text += " VARIABLE"
+
+ if charset == h5py.h5t.CSET_ASCII:
+ text += " ASCII"
+ elif charset == h5py.h5t.CSET_UTF8:
+ text += " UTF8"
+ else:
+ text += " UNKNOWN_CSET"
+
+ return text + " STRING"
+ elif class_ == h5py.h5t.BITFIELD:
+ return "BITFIELD"
+ elif class_ == h5py.h5t.OPAQUE:
+ return "OPAQUE"
+ elif class_ == h5py.h5t.COMPOUND:
+ return "COMPOUND"
+ elif class_ == h5py.h5t.REFERENCE:
+ return "REFERENCE"
+ elif class_ == h5py.h5t.ENUM:
+ return "ENUM"
+ elif class_ == h5py.h5t.VLEN:
+ return "VLEN"
+ elif class_ == h5py.h5t.ARRAY:
+ return "ARRAY"
+ else:
+ return "UNKNOWN_CLASS"
diff --git a/silx/gui/hdf5/Hdf5HeaderView.py b/silx/gui/hdf5/Hdf5HeaderView.py
index 5912230..7baa6e0 100644
--- a/silx/gui/hdf5/Hdf5HeaderView.py
+++ b/silx/gui/hdf5/Hdf5HeaderView.py
@@ -25,10 +25,11 @@
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "08/11/2016"
+__date__ = "16/06/2017"
from .. import qt
+from .Hdf5TreeModel import Hdf5TreeModel
QTVERSION = qt.qVersion()
@@ -83,19 +84,21 @@ class Hdf5HeaderView(qt.QHeaderView):
setResizeMode = self.setSectionResizeMode
if self.__auto_resize:
- setResizeMode(0, qt.QHeaderView.ResizeToContents)
- setResizeMode(1, qt.QHeaderView.ResizeToContents)
- setResizeMode(2, qt.QHeaderView.ResizeToContents)
- setResizeMode(3, qt.QHeaderView.Interactive)
- setResizeMode(4, qt.QHeaderView.Interactive)
- setResizeMode(5, qt.QHeaderView.ResizeToContents)
+ setResizeMode(Hdf5TreeModel.NAME_COLUMN, qt.QHeaderView.ResizeToContents)
+ setResizeMode(Hdf5TreeModel.TYPE_COLUMN, qt.QHeaderView.ResizeToContents)
+ setResizeMode(Hdf5TreeModel.SHAPE_COLUMN, qt.QHeaderView.ResizeToContents)
+ setResizeMode(Hdf5TreeModel.VALUE_COLUMN, qt.QHeaderView.Interactive)
+ setResizeMode(Hdf5TreeModel.DESCRIPTION_COLUMN, qt.QHeaderView.Interactive)
+ setResizeMode(Hdf5TreeModel.NODE_COLUMN, qt.QHeaderView.ResizeToContents)
+ setResizeMode(Hdf5TreeModel.LINK_COLUMN, qt.QHeaderView.ResizeToContents)
else:
- setResizeMode(0, qt.QHeaderView.Interactive)
- setResizeMode(1, qt.QHeaderView.Interactive)
- setResizeMode(2, qt.QHeaderView.Interactive)
- setResizeMode(3, qt.QHeaderView.Interactive)
- setResizeMode(4, qt.QHeaderView.Interactive)
- setResizeMode(5, qt.QHeaderView.Interactive)
+ setResizeMode(Hdf5TreeModel.NAME_COLUMN, qt.QHeaderView.Interactive)
+ setResizeMode(Hdf5TreeModel.TYPE_COLUMN, qt.QHeaderView.Interactive)
+ setResizeMode(Hdf5TreeModel.SHAPE_COLUMN, qt.QHeaderView.Interactive)
+ setResizeMode(Hdf5TreeModel.VALUE_COLUMN, qt.QHeaderView.Interactive)
+ setResizeMode(Hdf5TreeModel.DESCRIPTION_COLUMN, qt.QHeaderView.Interactive)
+ setResizeMode(Hdf5TreeModel.NODE_COLUMN, qt.QHeaderView.Interactive)
+ setResizeMode(Hdf5TreeModel.LINK_COLUMN, qt.QHeaderView.Interactive)
def setAutoResizeColumns(self, autoResize):
"""Enable/disable auto-resize. When auto-resized, the header take care
diff --git a/silx/gui/hdf5/Hdf5Item.py b/silx/gui/hdf5/Hdf5Item.py
index 40793a4..f131f61 100644
--- a/silx/gui/hdf5/Hdf5Item.py
+++ b/silx/gui/hdf5/Hdf5Item.py
@@ -25,10 +25,9 @@
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "20/01/2017"
+__date__ = "26/09/2017"
-import numpy
import logging
import collections
from .. import qt
@@ -37,6 +36,7 @@ from . import _utils
from .Hdf5Node import Hdf5Node
import silx.io.utils
from silx.gui.data.TextFormatter import TextFormatter
+from ..hdf5.Hdf5Formatter import Hdf5Formatter
_logger = logging.getLogger(__name__)
@@ -47,6 +47,8 @@ except ImportError as e:
raise e
_formatter = TextFormatter()
+_hdf5Formatter = Hdf5Formatter(textFormatter=_formatter)
+# FIXME: The formatter should be an attribute of the Hdf5Model
class Hdf5Item(Hdf5Node):
@@ -55,7 +57,7 @@ class Hdf5Item(Hdf5Node):
tree structure.
"""
- def __init__(self, text, obj, parent, key=None, h5pyClass=None, isBroken=False, populateAll=False):
+ def __init__(self, text, obj, parent, key=None, h5pyClass=None, linkClass=None, populateAll=False):
"""
:param str text: text displayed
:param object obj: Pointer to h5py data. See the `obj` attribute.
@@ -63,9 +65,10 @@ class Hdf5Item(Hdf5Node):
self.__obj = obj
self.__key = key
self.__h5pyClass = h5pyClass
- self.__isBroken = isBroken
+ self.__isBroken = obj is None and h5pyClass is None
self.__error = None
self.__text = text
+ self.__linkClass = linkClass
Hdf5Node.__init__(self, parent, populateAll=populateAll)
@property
@@ -88,16 +91,26 @@ class Hdf5Item(Hdf5Node):
:rtype: h5py.File or h5py.Dataset or h5py.Group
"""
- if self.__h5pyClass is None:
+ if self.__h5pyClass is None and self.obj is not None:
self.__h5pyClass = silx.io.utils.get_h5py_class(self.obj)
return self.__h5pyClass
+ @property
+ def linkClass(self):
+ """Returns the link class object of this node
+
+ :type: h5py.SoftLink or h5py.HardLink or h5py.ExternalLink or None
+ """
+ return self.__linkClass
+
def isGroupObj(self):
"""Returns true if the stored HDF5 object is a group (contains sub
groups or datasets).
:rtype: bool
"""
+ if self.h5pyClass is None:
+ return False
return issubclass(self.h5pyClass, h5py.Group)
def isBrokenObj(self):
@@ -111,6 +124,14 @@ class Hdf5Item(Hdf5Node):
"""
return self.__isBroken
+ def _getFormatter(self):
+ """
+ Returns an Hdf5Formatter
+
+ :rtype: Hdf5Formatter
+ """
+ return _hdf5Formatter
+
def _expectedChildCount(self):
if self.isGroupObj():
return len(self.obj)
@@ -158,6 +179,22 @@ class Hdf5Item(Hdf5Node):
self.__isBroken = True
else:
self.__obj = obj
+ if not self.isGroupObj():
+ try:
+ # pre-fetch of the data
+ if obj.shape is None:
+ pass
+ elif obj.shape == tuple():
+ obj[()]
+ else:
+ if obj.compression is None and obj.size > 0:
+ key = tuple([0] * len(obj.shape))
+ obj[key]
+ except Exception as e:
+ _logger.debug(e, exc_info=True)
+ message = "%s broken. %s" % (self.__obj.name, e.args[0])
+ self.__error = message
+ self.__isBroken = True
self.__key = None
@@ -166,15 +203,15 @@ class Hdf5Item(Hdf5Node):
for name in self.obj:
try:
class_ = self.obj.get(name, getclass=True)
- has_error = False
+ link = self.obj.get(name, getclass=True, getlink=True)
except Exception as e:
- _logger.error("Internal h5py error", exc_info=True)
+ _logger.warn("Internal h5py error", exc_info=True)
+ class_ = None
try:
- class_ = self.obj.get(name, getclass=True, getlink=True)
+ link = self.obj.get(name, getclass=True, getlink=True)
except Exception as e:
- class_ = h5py.HardLink
- has_error = True
- item = Hdf5Item(text=name, obj=None, parent=self, key=name, h5pyClass=class_, isBroken=has_error)
+ link = h5py.HardLink
+ item = Hdf5Item(text=name, obj=None, parent=self, key=name, h5pyClass=class_, linkClass=link)
self.appendChild(item)
def hasChildren(self):
@@ -191,6 +228,8 @@ class Hdf5Item(Hdf5Node):
:rtype: qt.QIcon
"""
+ # Pre-fetch the object, in case it is broken
+ obj = self.obj
style = qt.QApplication.style()
if self.__isBroken:
icon = style.standardIcon(qt.QStyle.SP_MessageBoxCritical)
@@ -205,99 +244,53 @@ class Hdf5Item(Hdf5Node):
elif issubclass(class_, h5py.ExternalLink):
return style.standardIcon(qt.QStyle.SP_FileLinkIcon)
elif issubclass(class_, h5py.Dataset):
- if len(self.obj.shape) < 4:
- name = "item-%ddim" % len(self.obj.shape)
+ if obj.shape is None:
+ name = "item-none"
+ elif len(obj.shape) < 4:
+ name = "item-%ddim" % len(obj.shape)
else:
name = "item-ndim"
- if str(self.obj.dtype) == "object":
- name = "item-object"
icon = icons.getQIcon(name)
return icon
return None
- def _humanReadableShape(self, dataset):
- if dataset.shape == tuple():
- return "scalar"
- shape = [str(i) for i in dataset.shape]
- text = u" \u00D7 ".join(shape)
- return text
-
- def _humanReadableValue(self, dataset):
- if dataset.shape == tuple():
- numpy_object = dataset[()]
- text = _formatter.toString(numpy_object)
- else:
- if dataset.size < 5 and dataset.compression is None:
- numpy_object = dataset[0:5]
- text = _formatter.toString(numpy_object)
- else:
- dimension = len(dataset.shape)
- if dataset.compression is not None:
- text = "Compressed %dD data" % dimension
- else:
- text = "%dD data" % dimension
- return text
-
- def _humanReadableDType(self, dtype, full=False):
- if dtype.type == numpy.string_:
- text = "string"
- elif dtype.type == numpy.unicode_:
- text = "string"
- elif dtype.type == numpy.object_:
- text = "object"
- elif dtype.type == numpy.bool_:
- text = "bool"
- elif dtype.type == numpy.void:
- if dtype.fields is None:
- text = "raw"
- else:
- if not full:
- text = "compound"
- else:
- compound = [d[0] for d in dtype.fields.values()]
- compound = [self._humanReadableDType(d) for d in compound]
- text = "compound(%s)" % ", ".join(compound)
- else:
- text = str(dtype)
- return text
-
- def _humanReadableType(self, dataset, full=False):
- return self._humanReadableDType(dataset.dtype, full)
-
- def _setTooltipAttributes(self, attributeDict):
+ def _createTooltipAttributes(self):
"""
Add key/value attributes that will be displayed in the item tooltip
:param Dict[str,str] attributeDict: Key/value attributes
"""
+ attributeDict = collections.OrderedDict()
+
if issubclass(self.h5pyClass, h5py.Dataset):
- attributeDict["Title"] = "HDF5 Dataset"
+ attributeDict["#Title"] = "HDF5 Dataset"
attributeDict["Name"] = self.basename
attributeDict["Path"] = self.obj.name
- attributeDict["Shape"] = self._humanReadableShape(self.obj)
- attributeDict["Value"] = self._humanReadableValue(self.obj)
- attributeDict["Data type"] = self._humanReadableType(self.obj, full=True)
+ attributeDict["Shape"] = self._getFormatter().humanReadableShape(self.obj)
+ attributeDict["Value"] = self._getFormatter().humanReadableValue(self.obj)
+ attributeDict["Data type"] = self._getFormatter().humanReadableType(self.obj, full=True)
elif issubclass(self.h5pyClass, h5py.Group):
- attributeDict["Title"] = "HDF5 Group"
+ attributeDict["#Title"] = "HDF5 Group"
attributeDict["Name"] = self.basename
attributeDict["Path"] = self.obj.name
elif issubclass(self.h5pyClass, h5py.File):
- attributeDict["Title"] = "HDF5 File"
+ attributeDict["#Title"] = "HDF5 File"
attributeDict["Name"] = self.basename
attributeDict["Path"] = "/"
elif isinstance(self.obj, h5py.ExternalLink):
- attributeDict["Title"] = "HDF5 External Link"
+ attributeDict["#Title"] = "HDF5 External Link"
attributeDict["Name"] = self.basename
attributeDict["Path"] = self.obj.name
attributeDict["Linked path"] = self.obj.path
attributeDict["Linked file"] = self.obj.filename
elif isinstance(self.obj, h5py.SoftLink):
- attributeDict["Title"] = "HDF5 Soft Link"
+ attributeDict["#Title"] = "HDF5 Soft Link"
attributeDict["Name"] = self.basename
attributeDict["Path"] = self.obj.name
attributeDict["Linked path"] = self.obj.path
else:
pass
+ return attributeDict
def _getDefaultTooltip(self):
"""Returns the default tooltip
@@ -308,10 +301,8 @@ class Hdf5Item(Hdf5Node):
self.obj # lazy loading of the object
return self.__error
- attrs = collections.OrderedDict()
- self._setTooltipAttributes(attrs)
-
- title = attrs.pop("Title", None)
+ attrs = self._createTooltipAttributes()
+ title = attrs.pop("#Title", None)
if len(attrs) > 0:
tooltip = _utils.htmlFromDict(attrs, title=title)
else:
@@ -342,7 +333,7 @@ class Hdf5Item(Hdf5Node):
return ""
class_ = self.h5pyClass
if issubclass(class_, h5py.Dataset):
- text = self._humanReadableType(self.obj)
+ text = self._getFormatter().humanReadableType(self.obj)
else:
text = ""
return text
@@ -361,7 +352,7 @@ class Hdf5Item(Hdf5Node):
class_ = self.h5pyClass
if not issubclass(class_, h5py.Dataset):
return ""
- return self._humanReadableShape(self.obj)
+ return self._getFormatter().humanReadableShape(self.obj)
return None
def dataValue(self, role):
@@ -375,7 +366,7 @@ class Hdf5Item(Hdf5Node):
return ""
if not issubclass(self.h5pyClass, h5py.Dataset):
return ""
- return self._humanReadableValue(self.obj)
+ return self._getFormatter().humanReadableValue(self.obj)
return None
def dataDescription(self, role):
@@ -412,10 +403,41 @@ class Hdf5Item(Hdf5Node):
if role == qt.Qt.TextAlignmentRole:
return qt.Qt.AlignTop | qt.Qt.AlignLeft
if role == qt.Qt.DisplayRole:
+ if self.isBrokenObj():
+ return ""
class_ = self.h5pyClass
text = class_.__name__.split(".")[-1]
return text
if role == qt.Qt.ToolTipRole:
class_ = self.h5pyClass
+ if class_ is None:
+ return ""
return "Class name: %s" % self.__class__
return None
+
+ def dataLink(self, role):
+ """Data for the link column
+
+ Overwrite it to implement the content of the 'link' column.
+
+ :rtype: qt.QVariant
+ """
+ if role == qt.Qt.DecorationRole:
+ return None
+ if role == qt.Qt.TextAlignmentRole:
+ return qt.Qt.AlignTop | qt.Qt.AlignLeft
+ if role == qt.Qt.DisplayRole:
+ link = self.linkClass
+ if link is None:
+ return ""
+ elif link is h5py.ExternalLink:
+ return "External"
+ elif link is h5py.SoftLink:
+ return "Soft"
+ elif link is h5py.HardLink:
+ return ""
+ else:
+ return link.__name__
+ if role == qt.Qt.ToolTipRole:
+ return None
+ return None
diff --git a/silx/gui/hdf5/Hdf5Node.py b/silx/gui/hdf5/Hdf5Node.py
index 31bb097..0fcb407 100644
--- a/silx/gui/hdf5/Hdf5Node.py
+++ b/silx/gui/hdf5/Hdf5Node.py
@@ -25,7 +25,9 @@
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "23/09/2016"
+__date__ = "16/06/2017"
+
+import weakref
class Hdf5Node(object):
@@ -43,7 +45,9 @@ class Hdf5Node(object):
everything is lazy loaded.
"""
self.__child = None
- self.__parent = parent
+ self.__parent = None
+ if parent is not None:
+ self.__parent = weakref.ref(parent)
if populateAll:
self.__child = []
self._populateChild(populateAll=True)
@@ -54,7 +58,12 @@ class Hdf5Node(object):
:rtype: Hdf5Node
"""
- return self.__parent
+ if self.__parent is None:
+ return None
+ parent = self.__parent()
+ if parent is None:
+ self.__parent = parent
+ return parent
def setParent(self, parent):
"""Redefine the parent of the node.
@@ -63,7 +72,10 @@ class Hdf5Node(object):
:param Hdf5Node parent: The new parent
"""
- self.__parent = parent
+ if parent is None:
+ self.__parent = None
+ else:
+ self.__parent = weakref.ref(parent)
def appendChild(self, child):
"""Append a child to the node.
@@ -208,3 +220,12 @@ class Hdf5Node(object):
:rtype: qt.QVariant
"""
return None
+
+ def dataLink(self, role):
+ """Data for the link column
+
+ Overwrite it to implement the content of the 'link' column.
+
+ :rtype: qt.QVariant
+ """
+ return None
diff --git a/silx/gui/hdf5/Hdf5TreeModel.py b/silx/gui/hdf5/Hdf5TreeModel.py
index fb5de06..41fa91c 100644
--- a/silx/gui/hdf5/Hdf5TreeModel.py
+++ b/silx/gui/hdf5/Hdf5TreeModel.py
@@ -25,7 +25,7 @@
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "19/12/2016"
+__date__ = "22/09/2017"
import os
@@ -71,6 +71,25 @@ else:
return x
+def _createRootLabel(h5obj):
+ """
+ Create label for the very first npde of the tree.
+
+ :param h5obj: The h5py object to display in the GUI
+ :type h5obj: h5py-like object
+ :rtpye: str
+ """
+ if silx_io.is_file(h5obj):
+ label = os.path.basename(h5obj.filename)
+ else:
+ filename = os.path.basename(h5obj.file.filename)
+ path = h5obj.name
+ if path.startswith("/"):
+ path = path[1:]
+ label = "%s::%s" % (filename, path)
+ return label
+
+
class LoadingItemRunnable(qt.QRunnable):
"""Runner to process item loading from a file"""
@@ -107,12 +126,7 @@ class LoadingItemRunnable(qt.QRunnable):
:param h5py.File h5obj: The h5py object to display in the GUI
:rtpye: Hdf5Node
"""
- if silx_io.is_file(h5obj):
- text = os.path.basename(h5obj.filename)
- else:
- filename = os.path.basename(h5obj.file.filename)
- path = h5obj.name
- text = "%s::%s" % (filename, path)
+ text = _createRootLabel(h5obj)
item = Hdf5Item(text=text, obj=h5obj, parent=oldItem.parent, populateAll=True)
return item
@@ -121,6 +135,7 @@ class LoadingItemRunnable(qt.QRunnable):
"""Process the file loading. The worker is used as holder
of the data and the signal. The result is sent as a signal.
"""
+ h5file = None
try:
h5file = silx_io.open(self.filename)
newItem = self.__loadItemTree(self.oldItem, h5file)
@@ -129,6 +144,8 @@ class LoadingItemRunnable(qt.QRunnable):
# Should be logged
error = e
newItem = None
+ if h5file is not None:
+ h5file.close()
# Take care of None value in case of PySide
newItem = _wrapNone(newItem)
@@ -174,6 +191,9 @@ class Hdf5TreeModel(qt.QAbstractItemModel):
NODE_COLUMN = 5
"""Column id containing HDF5 node type"""
+ LINK_COLUMN = 6
+ """Column id containing HDF5 link type"""
+
COLUMN_IDS = [
NAME_COLUMN,
TYPE_COLUMN,
@@ -181,20 +201,21 @@ class Hdf5TreeModel(qt.QAbstractItemModel):
VALUE_COLUMN,
DESCRIPTION_COLUMN,
NODE_COLUMN,
+ LINK_COLUMN,
]
"""List of logical columns available"""
def __init__(self, parent=None):
super(Hdf5TreeModel, self).__init__(parent)
- self.treeView = parent
- self.header_labels = [None] * 6
+ self.header_labels = [None] * len(self.COLUMN_IDS)
self.header_labels[self.NAME_COLUMN] = 'Name'
self.header_labels[self.TYPE_COLUMN] = 'Type'
self.header_labels[self.SHAPE_COLUMN] = 'Shape'
self.header_labels[self.VALUE_COLUMN] = 'Value'
self.header_labels[self.DESCRIPTION_COLUMN] = 'Description'
self.header_labels[self.NODE_COLUMN] = 'Node'
+ self.header_labels[self.LINK_COLUMN] = 'Link'
# Create items
self.__root = Hdf5Node()
@@ -205,14 +226,36 @@ class Hdf5TreeModel(qt.QAbstractItemModel):
self.__animatedIcon.iconChanged.connect(self.__updateLoadingItems)
self.__runnerSet = set([])
- # store used icons to avoid to avoid the cache to release it
+ # store used icons to avoid the cache to release it
self.__icons = []
+ self.__icons.append(icons.getQIcon("item-none"))
self.__icons.append(icons.getQIcon("item-0dim"))
self.__icons.append(icons.getQIcon("item-1dim"))
self.__icons.append(icons.getQIcon("item-2dim"))
self.__icons.append(icons.getQIcon("item-3dim"))
self.__icons.append(icons.getQIcon("item-ndim"))
- self.__icons.append(icons.getQIcon("item-object"))
+
+ self.__openedFiles = []
+ """Store the list of files opened by the model itself."""
+ # FIXME: It should managed one by one by Hdf5Item itself
+
+ def __del__(self):
+ self._closeOpened()
+ s = super(Hdf5TreeModel, self)
+ if hasattr(s, "__del__"):
+ # else it fail on Python 3
+ s.__del__()
+
+ def _closeOpened(self):
+ """Close files which was opened by this model.
+
+ This function may be removed in the future.
+
+ File are opened by the model when it was inserted using
+ `insertFileAsync`, `insertFile`, `appendFile`."""
+ for h5file in self.__openedFiles:
+ h5file.close()
+ self.__openedFiles = []
def __updateLoadingItems(self, icon):
for i in range(self.__root.childCount()):
@@ -240,6 +283,7 @@ class Hdf5TreeModel(qt.QAbstractItemModel):
self.__root.removeChildAtIndex(row)
self.endRemoveRows()
if newItem is not None:
+ self.__openedFiles.append(newItem.obj)
self.beginInsertRows(rootIndex, row, row)
self.__root.insertChild(row, newItem)
self.endInsertRows()
@@ -423,11 +467,13 @@ class Hdf5TreeModel(qt.QAbstractItemModel):
return node.dataDescription(role)
elif index.column() == self.NODE_COLUMN:
return node.dataNode(role)
+ elif index.column() == self.LINK_COLUMN:
+ return node.dataLink(role)
else:
return None
def columnCount(self, parent=qt.QModelIndex()):
- return len(self.header_labels)
+ return len(self.COLUMN_IDS)
def hasChildren(self, parent=qt.QModelIndex()):
node = self.nodeFromIndex(parent)
@@ -536,12 +582,7 @@ class Hdf5TreeModel(qt.QAbstractItemModel):
or any other class of h5py file structure.
"""
if text is None:
- if silx_io.is_file(h5pyObject):
- text = os.path.basename(h5pyObject.filename)
- else:
- filename = os.path.basename(h5pyObject.file.filename)
- path = h5pyObject.name
- text = "%s::%s" % (filename, path)
+ text = _createRootLabel(h5pyObject)
if row == -1:
row = self.__root.childCount()
self.insertNode(row, Hdf5Item(text=text, obj=h5pyObject, parent=self.__root))
@@ -572,6 +613,7 @@ class Hdf5TreeModel(qt.QAbstractItemModel):
"""
try:
h5file = silx_io.open(filename)
+ self.__openedFiles.append(h5file)
self.insertH5pyObject(h5file, row=row)
except IOError:
_logger.debug("File '%s' can't be read.", filename, exc_info=True)
diff --git a/silx/gui/hdf5/Hdf5TreeView.py b/silx/gui/hdf5/Hdf5TreeView.py
index 09f6fcf..0a4198e 100644
--- a/silx/gui/hdf5/Hdf5TreeView.py
+++ b/silx/gui/hdf5/Hdf5TreeView.py
@@ -25,7 +25,7 @@
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "27/09/2016"
+__date__ = "20/09/2017"
import logging
@@ -43,6 +43,8 @@ _logger = logging.getLogger(__name__)
class Hdf5TreeView(qt.QTreeView):
"""TreeView which allow to browse HDF5 file structure.
+ .. image:: img/Hdf5TreeView.png
+
It provides columns width auto-resizing and additional
signals.
@@ -192,6 +194,87 @@ class Hdf5TreeView(qt.QTreeView):
continue
yield _utils.H5Node(item)
+ def setSelectedH5Node(self, h5Object):
+ """
+ Select the specified node of the tree using an h5py node.
+
+ - If the item is found, parent items are expended, and then the item
+ is selected.
+ - If the item is not found, the selection do not change.
+ - A none argument allow to deselect everything
+
+ :param h5py.Npde h5Object: The node to select
+ """
+ if h5Object is None:
+ self.setCurrentIndex(qt.QModelIndex())
+ return
+
+ filename = h5Object.file.filename
+
+ # Seach for the right roots
+ rootIndices = []
+ model = self.model()
+ for index in range(model.rowCount(qt.QModelIndex())):
+ index = model.index(index, 0, qt.QModelIndex())
+ obj = model.data(index, Hdf5TreeModel.H5PY_OBJECT_ROLE)
+ if obj.file.filename == filename:
+ # We can have many roots with different subtree of the same
+ # root
+ rootIndices.append(index)
+
+ if len(rootIndices) == 0:
+ # No root found
+ return
+
+ path = h5Object.name + "/"
+ path = path.replace("//", "/")
+
+ # Search for the right node
+ found = False
+ foundIndices = []
+ for _ in range(1000 * len(rootIndices)):
+ # Avoid too much iterations, in case of recurssive links
+ if len(foundIndices) == 0:
+ if len(rootIndices) == 0:
+ # Nothing found
+ break
+ # Start fron a new root
+ foundIndices.append(rootIndices.pop(0))
+
+ obj = model.data(index, Hdf5TreeModel.H5PY_OBJECT_ROLE)
+ p = obj.name + "/"
+ p = p.replace("//", "/")
+ if path == p:
+ found = True
+ break
+
+ parentIndex = foundIndices[-1]
+ for index in range(model.rowCount(parentIndex)):
+ index = model.index(index, 0, parentIndex)
+ obj = model.data(index, Hdf5TreeModel.H5PY_OBJECT_ROLE)
+
+ p = obj.name + "/"
+ p = p.replace("//", "/")
+ if path == p:
+ foundIndices.append(index)
+ found = True
+ break
+ elif path.startswith(p):
+ foundIndices.append(index)
+ break
+ else:
+ # Nothing found, start again with another root
+ foundIndices = []
+
+ if found:
+ break
+
+ if found:
+ # Update the GUI
+ for index in foundIndices[:-1]:
+ self.expand(index)
+ self.setCurrentIndex(foundIndices[-1])
+
def mousePressEvent(self, event):
"""Override mousePressEvent to provide a consistante compatible API
between Qt4 and Qt5
diff --git a/silx/gui/hdf5/NexusSortFilterProxyModel.py b/silx/gui/hdf5/NexusSortFilterProxyModel.py
index 9a4268c..49a22d3 100644
--- a/silx/gui/hdf5/NexusSortFilterProxyModel.py
+++ b/silx/gui/hdf5/NexusSortFilterProxyModel.py
@@ -25,7 +25,7 @@
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "12/04/2017"
+__date__ = "16/06/2017"
import logging
@@ -86,7 +86,8 @@ class NexusSortFilterProxyModel(qt.QSortFilterProxyModel):
def __isNXentry(self, node):
"""Returns true if the node is an NXentry"""
- if not issubclass(node.h5pyClass, h5py.Group):
+ class_ = node.h5pyClass
+ if class_ is None or not issubclass(node.h5pyClass, h5py.Group):
return False
nxClass = node.obj.attrs.get("NX_class", None)
return nxClass == "NXentry"
diff --git a/silx/gui/hdf5/_utils.py b/silx/gui/hdf5/_utils.py
index af9c79f..048aa20 100644
--- a/silx/gui/hdf5/_utils.py
+++ b/silx/gui/hdf5/_utils.py
@@ -28,11 +28,10 @@ package `silx.gui.hdf5` package.
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "26/04/2017"
+__date__ = "29/09/2017"
import logging
-import numpy
from .. import qt
import silx.io.utils
from silx.utils.html import escape
@@ -138,10 +137,61 @@ class H5Node(object):
:param Hdf5Item h5py_item: An Hdf5Item
"""
self.__h5py_object = h5py_item.obj
+ self.__h5py_target = None
self.__h5py_item = h5py_item
def __getattr__(self, name):
- return object.__getattribute__(self.__h5py_object, name)
+ if hasattr(self.__h5py_object, name):
+ attr = getattr(self.__h5py_object, name)
+ return attr
+ raise AttributeError("H5Node has no attribute %s" % name)
+
+ def __get_target(self, obj):
+ """
+ Return the actual physical target of the provided object.
+
+ Objects can contains links in the middle of the path, this function
+ check each groups and remove this prefix in case of the link by the
+ link of the path.
+
+ :param obj: A valid h5py object (File, group or dataset)
+ :type obj: h5py.Dataset or h5py.Group or h5py.File
+ :rtype: h5py.Dataset or h5py.Group or h5py.File
+ """
+ elements = obj.name.split("/")
+ if obj.name == "/":
+ return obj
+ elif obj.name.startswith("/"):
+ elements.pop(0)
+ path = ""
+ while len(elements) > 0:
+ e = elements.pop(0)
+ path = path + "/" + e
+ link = obj.parent.get(path, getlink=True)
+
+ if isinstance(link, h5py.ExternalLink):
+ subpath = "/".join(elements)
+ external_obj = obj.parent.get(self.basename + "/" + subpath)
+ return self.__get_target(external_obj)
+ elif silx.io.utils.is_softlink(link):
+ # Restart from this stat
+ path = ""
+ root_elements = link.path.split("/")
+ if link.path == "/":
+ root_elements = []
+ elif link.path.startswith("/"):
+ root_elements.pop(0)
+ for name in reversed(root_elements):
+ elements.insert(0, name)
+
+ return obj.file[path]
+
+ @property
+ def h5py_target(self):
+ if self.__h5py_target is not None:
+ return self.__h5py_target
+ self.__h5py_target = self.__get_target(self.__h5py_object)
+ return self.__h5py_target
@property
def h5py_object(self):
@@ -170,8 +220,18 @@ class H5Node(object):
return self.__h5py_object.name.split("/")[-1]
@property
+ def is_broken(self):
+ """Returns true if the node is a broken link.
+
+ :rtype: bool
+ """
+ if self.__h5py_item is None:
+ raise RuntimeError("h5py_item is not defined")
+ return self.__h5py_item.isBrokenObj()
+
+ @property
def local_name(self):
- """Returns the local path of this h5py node.
+ """Returns the path from the master file root to this node.
For links, this path is not equal to the h5py one.
@@ -183,34 +243,46 @@ class H5Node(object):
result = []
item = self.__h5py_item
while item is not None:
- if issubclass(item.h5pyClass, h5py.File):
+ # stop before the root item (item without parent)
+ if item.parent.parent is None:
+ name = item.obj.name
+ if name != "/":
+ result.append(item.obj.name)
break
- result.append(item.basename)
+ else:
+ result.append(item.basename)
item = item.parent
if item is None:
raise RuntimeError("The item does not have parent holding h5py.File")
if result == []:
return "/"
- result.append("")
+ if not result[-1].startswith("/"):
+ result.append("")
result.reverse()
- return "/".join(result)
+ name = "/".join(result)
+ return name
- def __file_item(self):
- """Returns the parent item holding the :class:`h5py.File` object
+ def __get_local_file(self):
+ """Returns the file of the root of this tree
:rtype: h5py.File
- :raises RuntimeException: If no file are found
"""
item = self.__h5py_item
- while item is not None:
- if issubclass(item.h5pyClass, h5py.File):
- return item
+ while item.parent.parent is not None:
+ class_ = item.h5pyClass
+ if class_ is not None and issubclass(class_, h5py.File):
+ break
item = item.parent
- raise RuntimeError("The item does not have parent holding h5py.File")
+
+ class_ = item.h5pyClass
+ if class_ is not None and issubclass(class_, h5py.File):
+ return item.obj
+ else:
+ return item.obj.file
@property
def local_file(self):
- """Returns the local :class:`h5py.File` object.
+ """Returns the master file in which is this node.
For path containing external links, this file is not equal to the h5py
one.
@@ -218,12 +290,11 @@ class H5Node(object):
:rtype: h5py.File
:raises RuntimeException: If no file are found
"""
- item = self.__file_item()
- return item.obj
+ return self.__get_local_file()
@property
def local_filename(self):
- """Returns the local filename of the h5py node.
+ """Returns the filename from the master file of this node.
For path containing external links, this path is not equal to the
filename provided by h5py.
@@ -235,13 +306,84 @@ class H5Node(object):
@property
def local_basename(self):
- """Returns the local filename of the h5py node.
+ """Returns the basename from the master file root to this node.
For path containing links, this basename can be different than the
basename provided by h5py.
:rtype: str
"""
- if issubclass(self.__h5py_item.h5pyClass, h5py.File):
+ class_ = self.__h5py_item.h5pyClass
+ if class_ is not None and issubclass(class_, h5py.File):
return ""
return self.__h5py_item.basename
+
+ @property
+ def physical_file(self):
+ """Returns the physical file in which is this node.
+
+ .. versionadded:: 0.6
+
+ :rtype: h5py.File
+ :raises RuntimeError: If no file are found
+ """
+ if isinstance(self.__h5py_object, h5py.ExternalLink):
+ # It means the link is broken
+ raise RuntimeError("No file node found")
+ if isinstance(self.__h5py_object, h5py.SoftLink):
+ # It means the link is broken
+ return self.local_file
+
+ physical_obj = self.h5py_target
+ return physical_obj.file
+
+ @property
+ def physical_name(self):
+ """Returns the path from the location this h5py node is physically
+ stored.
+
+ For broken links, this filename can be different from the
+ filename provided by h5py.
+
+ :rtype: str
+ """
+ if isinstance(self.__h5py_object, h5py.ExternalLink):
+ # It means the link is broken
+ return self.__h5py_object.path
+ if isinstance(self.__h5py_object, h5py.SoftLink):
+ # It means the link is broken
+ return self.__h5py_object.path
+
+ physical_obj = self.h5py_target
+ return physical_obj.name
+
+ @property
+ def physical_filename(self):
+ """Returns the filename from the location this h5py node is physically
+ stored.
+
+ For broken links, this filename can be different from the
+ filename provided by h5py.
+
+ :rtype: str
+ """
+ if isinstance(self.__h5py_object, h5py.ExternalLink):
+ # It means the link is broken
+ return self.__h5py_object.filename
+ if isinstance(self.__h5py_object, h5py.SoftLink):
+ # It means the link is broken
+ return self.local_file.filename
+
+ return self.physical_file.filename
+
+ @property
+ def physical_basename(self):
+ """Returns the basename from the location this h5py node is physically
+ stored.
+
+ For broken links, this basename can be different from the
+ basename provided by h5py.
+
+ :rtype: str
+ """
+ return self.physical_name.split("/")[-1]
diff --git a/silx/gui/hdf5/test/_mock.py b/silx/gui/hdf5/test/_mock.py
deleted file mode 100644
index eada590..0000000
--- a/silx/gui/hdf5/test/_mock.py
+++ /dev/null
@@ -1,130 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016-2017 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.
-#
-# ###########################################################################*/
-"""Mock for silx.gui.hdf5 module"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "12/04/2017"
-
-
-import numpy
-try:
- import h5py
-except ImportError:
- h5py = None
-
-
-class Node(object):
-
- def __init__(self, basename, parent, h5py_class):
- self.basename = basename
- self.h5py_class = h5py_class
- self.attrs = {}
- self.parent = parent
- if parent is not None:
- self.parent._add(self)
-
- @property
- def name(self):
- if self.parent is None:
- return self.basename
- if self.parent.name == "":
- return self.basename
- return self.parent.name + "/" + self.basename
-
- @property
- def file(self):
- if self.parent is None:
- return self
- return self.parent.file
-
-
-class Group(Node):
- """Mock an h5py Group"""
-
- def __init__(self, name, parent, h5py_class=h5py.Group):
- super(Group, self).__init__(name, parent, h5py_class)
- self.__items = {}
-
- def _add(self, node):
- self.__items[node.basename] = node
-
- def __getitem__(self, key):
- return self.__items[key]
-
- def __iter__(self):
- for k in self.__items:
- yield k
-
- def __len__(self):
- return len(self.__items)
-
- def get(self, name, getclass=False, getlink=False):
- result = self.__items[name]
- if getclass:
- return result.h5py_class
- return result
-
- def create_dataset(self, name, data):
- return Dataset(name, self, data)
-
- def create_group(self, name):
- return Group(name, self)
-
- def create_NXentry(self, name):
- group = Group(name, self)
- group.attrs["NX_class"] = "NXentry"
- return group
-
-
-class File(Group):
- """Mock an h5py File"""
-
- def __init__(self, filename):
- super(File, self).__init__("", None, h5py.File)
- self.filename = filename
-
-
-class Dataset(Node):
- """Mock an h5py Dataset"""
-
- def __init__(self, name, parent, value):
- super(Dataset, self).__init__(name, parent, h5py.Dataset)
- self.__value = value
- self.shape = self.__value.shape
- self.dtype = self.__value.dtype
- self.size = self.__value.size
- self.compression = None
- self.compression_opts = None
-
- def __getitem__(self, key):
- if not isinstance(self.__value, numpy.ndarray):
- if key == tuple():
- return self.__value
- elif key == Ellipsis:
- return numpy.array(self.__value)
- else:
- raise ValueError("Bad key")
- return self.__value[key]
diff --git a/silx/gui/hdf5/test/test_hdf5.py b/silx/gui/hdf5/test/test_hdf5.py
index 3bf4897..8e375f2 100644
--- a/silx/gui/hdf5/test/test_hdf5.py
+++ b/silx/gui/hdf5/test/test_hdf5.py
@@ -26,7 +26,7 @@
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "12/04/2017"
+__date__ = "22/09/2017"
import time
@@ -34,11 +34,12 @@ import os
import unittest
import tempfile
import numpy
+import shutil
from contextlib import contextmanager
from silx.gui import qt
from silx.gui.test.utils import TestCaseQt
from silx.gui import hdf5
-from . import _mock
+from silx.io import commonh5
try:
import h5py
@@ -54,6 +55,13 @@ class _Holder(object):
_called += 1
+def create_NXentry(group, name):
+ attrs = {"NX_class": "NXentry"}
+ node = commonh5.Group(name, parent=group, attrs=attrs)
+ group.add_node(node)
+ return node
+
+
class TestHdf5TreeModel(TestCaseQt):
def setUp(self):
@@ -124,14 +132,14 @@ class TestHdf5TreeModel(TestCaseQt):
h5File.close()
def testInsertObject(self):
- h5 = _mock.File("/foo/bar/1.mock")
+ h5 = commonh5.File("/foo/bar/1.mock", "w")
model = hdf5.Hdf5TreeModel()
self.assertEquals(model.rowCount(qt.QModelIndex()), 0)
model.insertH5pyObject(h5)
self.assertEquals(model.rowCount(qt.QModelIndex()), 1)
def testRemoveObject(self):
- h5 = _mock.File("/foo/bar/1.mock")
+ h5 = commonh5.File("/foo/bar/1.mock", "w")
model = hdf5.Hdf5TreeModel()
self.assertEquals(model.rowCount(qt.QModelIndex()), 0)
model.insertH5pyObject(h5)
@@ -223,7 +231,7 @@ class TestHdf5TreeModel(TestCaseQt):
return model.data(index, qt.Qt.DisplayRole)
def testFileData(self):
- h5 = _mock.File("/foo/bar/1.mock")
+ h5 = commonh5.File("/foo/bar/1.mock", "w")
model = hdf5.Hdf5TreeModel()
model.insertH5pyObject(h5)
displayed = self.getRowDataAsDict(model, row=0)
@@ -236,7 +244,7 @@ class TestHdf5TreeModel(TestCaseQt):
self.assertEquals(displayed[hdf5.Hdf5TreeModel.NODE_COLUMN, qt.Qt.DisplayRole], "File")
def testGroupData(self):
- h5 = _mock.File("/foo/bar/1.mock")
+ h5 = commonh5.File("/foo/bar/1.mock", "w")
d = h5.create_group("foo")
d.attrs["desc"] = "fooo"
@@ -252,9 +260,9 @@ class TestHdf5TreeModel(TestCaseQt):
self.assertEquals(displayed[hdf5.Hdf5TreeModel.NODE_COLUMN, qt.Qt.DisplayRole], "Group")
def testDatasetData(self):
- h5 = _mock.File("/foo/bar/1.mock")
+ h5 = commonh5.File("/foo/bar/1.mock", "w")
value = numpy.array([1, 2, 3])
- d = h5.create_dataset("foo", value)
+ d = h5.create_dataset("foo", data=value)
model = hdf5.Hdf5TreeModel()
model.insertH5pyObject(d)
@@ -269,8 +277,8 @@ class TestHdf5TreeModel(TestCaseQt):
def testDropLastAsFirst(self):
model = hdf5.Hdf5TreeModel()
- h5_1 = _mock.File("/foo/bar/1.mock")
- h5_2 = _mock.File("/foo/bar/2.mock")
+ h5_1 = commonh5.File("/foo/bar/1.mock", "w")
+ h5_2 = commonh5.File("/foo/bar/2.mock", "w")
model.insertH5pyObject(h5_1)
model.insertH5pyObject(h5_2)
self.assertEquals(self.getItemName(model, 0), "1.mock")
@@ -283,8 +291,8 @@ class TestHdf5TreeModel(TestCaseQt):
def testDropFirstAsLast(self):
model = hdf5.Hdf5TreeModel()
- h5_1 = _mock.File("/foo/bar/1.mock")
- h5_2 = _mock.File("/foo/bar/2.mock")
+ h5_1 = commonh5.File("/foo/bar/1.mock", "w")
+ h5_2 = commonh5.File("/foo/bar/2.mock", "w")
model.insertH5pyObject(h5_1)
model.insertH5pyObject(h5_2)
self.assertEquals(self.getItemName(model, 0), "1.mock")
@@ -297,7 +305,7 @@ class TestHdf5TreeModel(TestCaseQt):
def testRootParent(self):
model = hdf5.Hdf5TreeModel()
- h5_1 = _mock.File("/foo/bar/1.mock")
+ h5_1 = commonh5.File("/foo/bar/1.mock", "w")
model.insertH5pyObject(h5_1)
index = model.index(0, 0, qt.QModelIndex())
index = model.parent(index)
@@ -318,10 +326,10 @@ class TestNexusSortFilterProxyModel(TestCaseQt):
def testNXentryStartTime(self):
"""Test NXentry with start_time"""
model = hdf5.Hdf5TreeModel()
- h5 = _mock.File("/foo/bar/1.mock")
- h5.create_NXentry("a").create_dataset("start_time", numpy.string_("2015"))
- h5.create_NXentry("b").create_dataset("start_time", numpy.string_("2013"))
- h5.create_NXentry("c").create_dataset("start_time", numpy.string_("2014"))
+ h5 = commonh5.File("/foo/bar/1.mock", "w")
+ create_NXentry(h5, "a").create_dataset("start_time", data=numpy.string_("2015"))
+ create_NXentry(h5, "b").create_dataset("start_time", data=numpy.string_("2013"))
+ create_NXentry(h5, "c").create_dataset("start_time", data=numpy.string_("2014"))
model.insertH5pyObject(h5)
proxy = hdf5.NexusSortFilterProxyModel()
@@ -333,10 +341,10 @@ class TestNexusSortFilterProxyModel(TestCaseQt):
def testNXentryStartTimeInArray(self):
"""Test NXentry with start_time"""
model = hdf5.Hdf5TreeModel()
- h5 = _mock.File("/foo/bar/1.mock")
- h5.create_NXentry("a").create_dataset("start_time", numpy.array([numpy.string_("2015")]))
- h5.create_NXentry("b").create_dataset("start_time", numpy.array([numpy.string_("2013")]))
- h5.create_NXentry("c").create_dataset("start_time", numpy.array([numpy.string_("2014")]))
+ h5 = commonh5.File("/foo/bar/1.mock", "w")
+ create_NXentry(h5, "a").create_dataset("start_time", data=numpy.array([numpy.string_("2015")]))
+ create_NXentry(h5, "b").create_dataset("start_time", data=numpy.array([numpy.string_("2013")]))
+ create_NXentry(h5, "c").create_dataset("start_time", data=numpy.array([numpy.string_("2014")]))
model.insertH5pyObject(h5)
proxy = hdf5.NexusSortFilterProxyModel()
@@ -348,10 +356,10 @@ class TestNexusSortFilterProxyModel(TestCaseQt):
def testNXentryEndTimeInArray(self):
"""Test NXentry with end_time"""
model = hdf5.Hdf5TreeModel()
- h5 = _mock.File("/foo/bar/1.mock")
- h5.create_NXentry("a").create_dataset("end_time", numpy.array([numpy.string_("2015")]))
- h5.create_NXentry("b").create_dataset("end_time", numpy.array([numpy.string_("2013")]))
- h5.create_NXentry("c").create_dataset("end_time", numpy.array([numpy.string_("2014")]))
+ h5 = commonh5.File("/foo/bar/1.mock", "w")
+ create_NXentry(h5, "a").create_dataset("end_time", data=numpy.array([numpy.string_("2015")]))
+ create_NXentry(h5, "b").create_dataset("end_time", data=numpy.array([numpy.string_("2013")]))
+ create_NXentry(h5, "c").create_dataset("end_time", data=numpy.array([numpy.string_("2014")]))
model.insertH5pyObject(h5)
proxy = hdf5.NexusSortFilterProxyModel()
@@ -363,10 +371,10 @@ class TestNexusSortFilterProxyModel(TestCaseQt):
def testNXentryName(self):
"""Test NXentry without start_time or end_time"""
model = hdf5.Hdf5TreeModel()
- h5 = _mock.File("/foo/bar/1.mock")
- h5.create_NXentry("a")
- h5.create_NXentry("c")
- h5.create_NXentry("b")
+ h5 = commonh5.File("/foo/bar/1.mock", "w")
+ create_NXentry(h5, "a")
+ create_NXentry(h5, "c")
+ create_NXentry(h5, "b")
model.insertH5pyObject(h5)
proxy = hdf5.NexusSortFilterProxyModel()
@@ -378,10 +386,10 @@ class TestNexusSortFilterProxyModel(TestCaseQt):
def testStartTime(self):
"""If it is not NXentry, start_time is not used"""
model = hdf5.Hdf5TreeModel()
- h5 = _mock.File("/foo/bar/1.mock")
- h5.create_group("a").create_dataset("start_time", numpy.string_("2015"))
- h5.create_group("b").create_dataset("start_time", numpy.string_("2013"))
- h5.create_group("c").create_dataset("start_time", numpy.string_("2014"))
+ h5 = commonh5.File("/foo/bar/1.mock", "w")
+ h5.create_group("a").create_dataset("start_time", data=numpy.string_("2015"))
+ h5.create_group("b").create_dataset("start_time", data=numpy.string_("2013"))
+ h5.create_group("c").create_dataset("start_time", data=numpy.string_("2014"))
model.insertH5pyObject(h5)
proxy = hdf5.NexusSortFilterProxyModel()
@@ -392,7 +400,7 @@ class TestNexusSortFilterProxyModel(TestCaseQt):
def testName(self):
model = hdf5.Hdf5TreeModel()
- h5 = _mock.File("/foo/bar/1.mock")
+ h5 = commonh5.File("/foo/bar/1.mock", "w")
h5.create_group("a")
h5.create_group("c")
h5.create_group("b")
@@ -406,7 +414,7 @@ class TestNexusSortFilterProxyModel(TestCaseQt):
def testNumber(self):
model = hdf5.Hdf5TreeModel()
- h5 = _mock.File("/foo/bar/1.mock")
+ h5 = commonh5.File("/foo/bar/1.mock", "w")
h5.create_group("a1")
h5.create_group("a20")
h5.create_group("a3")
@@ -420,7 +428,7 @@ class TestNexusSortFilterProxyModel(TestCaseQt):
def testMultiNumber(self):
model = hdf5.Hdf5TreeModel()
- h5 = _mock.File("/foo/bar/1.mock")
+ h5 = commonh5.File("/foo/bar/1.mock", "w")
h5.create_group("a1-1")
h5.create_group("a20-1")
h5.create_group("a3-1")
@@ -436,7 +444,7 @@ class TestNexusSortFilterProxyModel(TestCaseQt):
def testUnconsistantTypes(self):
model = hdf5.Hdf5TreeModel()
- h5 = _mock.File("/foo/bar/1.mock")
+ h5 = commonh5.File("/foo/bar/1.mock", "w")
h5.create_group("aaa100")
h5.create_group("100aaa")
model.insertH5pyObject(h5)
@@ -448,11 +456,235 @@ class TestNexusSortFilterProxyModel(TestCaseQt):
self.assertListEqual(names, ["100aaa", "aaa100"])
-class TestHdf5(TestCaseQt):
+class TestH5Node(TestCaseQt):
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestH5Node, cls).setUpClass()
+ if h5py is None:
+ raise unittest.SkipTest("h5py is not available")
+
+ cls.tmpDirectory = tempfile.mkdtemp()
+ cls.h5Filename = cls.createResource(cls.tmpDirectory)
+ cls.h5File = h5py.File(cls.h5Filename, mode="r")
+ cls.model = cls.createModel(cls.h5File)
+
+ @classmethod
+ def createResource(cls, directory):
+ filename = os.path.join(directory, "base.h5")
+ externalFilename = os.path.join(directory, "base__external.h5")
+
+ externalh5 = h5py.File(externalFilename, mode="w")
+ externalh5["target/dataset"] = 50
+ externalh5["target/link"] = h5py.SoftLink("/target/dataset")
+ externalh5.close()
+
+ h5 = h5py.File(filename, mode="w")
+ h5["group/dataset"] = 50
+ h5["link/soft_link"] = h5py.SoftLink("/group/dataset")
+ h5["link/soft_link_to_group"] = h5py.SoftLink("/group")
+ h5["link/soft_link_to_link"] = h5py.SoftLink("/link/soft_link")
+ h5["link/soft_link_to_file"] = h5py.SoftLink("/")
+ h5["link/external_link"] = h5py.ExternalLink(externalFilename, "/target/dataset")
+ h5["link/external_link_to_link"] = h5py.ExternalLink(externalFilename, "/target/link")
+ h5["broken_link/external_broken_file"] = h5py.ExternalLink(externalFilename + "_not_exists", "/target/link")
+ h5["broken_link/external_broken_link"] = h5py.ExternalLink(externalFilename, "/target/not_exists")
+ h5["broken_link/soft_broken_link"] = h5py.SoftLink("/group/not_exists")
+ h5["broken_link/soft_link_to_broken_link"] = h5py.SoftLink("/group/not_exists")
+ h5.close()
+
+ return filename
+
+ @classmethod
+ def createModel(cls, h5pyFile):
+ model = hdf5.Hdf5TreeModel()
+ model.insertH5pyObject(h5pyFile)
+ return model
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.model = None
+ cls.h5File.close()
+ shutil.rmtree(cls.tmpDirectory)
+ super(TestH5Node, cls).tearDownClass()
+
+ def getIndexFromPath(self, model, path):
+ """
+ :param qt.QAbstractItemModel: model
+ """
+ index = qt.QModelIndex()
+ for name in path:
+ for row in range(model.rowCount(index)):
+ i = model.index(row, 0, index)
+ label = model.data(i)
+ if label == name:
+ index = i
+ break
+ else:
+ raise RuntimeError("Path not found")
+ return index
+
+ def getH5NodeFromPath(self, model, path):
+ index = self.getIndexFromPath(model, path)
+ item = model.data(index, hdf5.Hdf5TreeModel.H5PY_ITEM_ROLE)
+ h5node = hdf5.H5Node(item)
+ return h5node
+
+ def testFile(self):
+ path = ["base.h5"]
+ h5node = self.getH5NodeFromPath(self.model, path)
+
+ self.assertEqual(h5node.physical_filename, h5node.local_filename)
+ self.assertIn("base.h5", h5node.physical_filename)
+ self.assertEqual(h5node.physical_basename, "")
+ self.assertEqual(h5node.physical_name, "/")
+ self.assertEqual(h5node.local_basename, "")
+ self.assertEqual(h5node.local_name, "/")
+
+ def testGroup(self):
+ path = ["base.h5", "group"]
+ h5node = self.getH5NodeFromPath(self.model, path)
+
+ self.assertEqual(h5node.physical_filename, h5node.local_filename)
+ self.assertIn("base.h5", h5node.physical_filename)
+ self.assertEqual(h5node.physical_basename, "group")
+ self.assertEqual(h5node.physical_name, "/group")
+ self.assertEqual(h5node.local_basename, "group")
+ self.assertEqual(h5node.local_name, "/group")
+
+ def testDataset(self):
+ path = ["base.h5", "group", "dataset"]
+ h5node = self.getH5NodeFromPath(self.model, path)
+
+ self.assertEqual(h5node.physical_filename, h5node.local_filename)
+ self.assertIn("base.h5", h5node.physical_filename)
+ self.assertEqual(h5node.physical_basename, "dataset")
+ self.assertEqual(h5node.physical_name, "/group/dataset")
+ self.assertEqual(h5node.local_basename, "dataset")
+ self.assertEqual(h5node.local_name, "/group/dataset")
+
+ def testSoftLink(self):
+ path = ["base.h5", "link", "soft_link"]
+ h5node = self.getH5NodeFromPath(self.model, path)
+
+ self.assertEqual(h5node.physical_filename, h5node.local_filename)
+ self.assertIn("base.h5", h5node.physical_filename)
+ self.assertEqual(h5node.physical_basename, "dataset")
+ self.assertEqual(h5node.physical_name, "/group/dataset")
+ self.assertEqual(h5node.local_basename, "soft_link")
+ self.assertEqual(h5node.local_name, "/link/soft_link")
+
+ def testSoftLinkToLink(self):
+ path = ["base.h5", "link", "soft_link_to_link"]
+ h5node = self.getH5NodeFromPath(self.model, path)
+
+ self.assertEqual(h5node.physical_filename, h5node.local_filename)
+ self.assertIn("base.h5", h5node.physical_filename)
+ self.assertEqual(h5node.physical_basename, "dataset")
+ self.assertEqual(h5node.physical_name, "/group/dataset")
+ self.assertEqual(h5node.local_basename, "soft_link_to_link")
+ self.assertEqual(h5node.local_name, "/link/soft_link_to_link")
+
+ def testExternalLink(self):
+ path = ["base.h5", "link", "external_link"]
+ h5node = self.getH5NodeFromPath(self.model, path)
+
+ self.assertNotEqual(h5node.physical_filename, h5node.local_filename)
+ self.assertIn("base.h5", h5node.local_filename)
+ self.assertIn("base__external.h5", h5node.physical_filename)
+ self.assertEqual(h5node.physical_basename, "dataset")
+ self.assertEqual(h5node.physical_name, "/target/dataset")
+ self.assertEqual(h5node.local_basename, "external_link")
+ self.assertEqual(h5node.local_name, "/link/external_link")
+
+ def testExternalLinkToLink(self):
+ path = ["base.h5", "link", "external_link_to_link"]
+ h5node = self.getH5NodeFromPath(self.model, path)
+
+ self.assertNotEqual(h5node.physical_filename, h5node.local_filename)
+ self.assertIn("base.h5", h5node.local_filename)
+ self.assertIn("base__external.h5", h5node.physical_filename)
+
+ self.assertNotEqual(h5node.physical_filename, h5node.local_filename)
+ self.assertEqual(h5node.physical_basename, "dataset")
+ self.assertEqual(h5node.physical_name, "/target/dataset")
+ self.assertEqual(h5node.local_basename, "external_link_to_link")
+ self.assertEqual(h5node.local_name, "/link/external_link_to_link")
+
+ def testExternalBrokenFile(self):
+ path = ["base.h5", "broken_link", "external_broken_file"]
+ h5node = self.getH5NodeFromPath(self.model, path)
+
+ self.assertNotEqual(h5node.physical_filename, h5node.local_filename)
+ self.assertIn("base.h5", h5node.local_filename)
+ self.assertIn("not_exists", h5node.physical_filename)
+ self.assertEqual(h5node.physical_basename, "link")
+ self.assertEqual(h5node.physical_name, "/target/link")
+ self.assertEqual(h5node.local_basename, "external_broken_file")
+ self.assertEqual(h5node.local_name, "/broken_link/external_broken_file")
+
+ def testExternalBrokenLink(self):
+ path = ["base.h5", "broken_link", "external_broken_link"]
+ h5node = self.getH5NodeFromPath(self.model, path)
+
+ self.assertNotEqual(h5node.physical_filename, h5node.local_filename)
+ self.assertIn("base.h5", h5node.local_filename)
+ self.assertIn("__external", h5node.physical_filename)
+ self.assertEqual(h5node.physical_basename, "not_exists")
+ self.assertEqual(h5node.physical_name, "/target/not_exists")
+ self.assertEqual(h5node.local_basename, "external_broken_link")
+ self.assertEqual(h5node.local_name, "/broken_link/external_broken_link")
+
+ def testSoftBrokenLink(self):
+ path = ["base.h5", "broken_link", "soft_broken_link"]
+ h5node = self.getH5NodeFromPath(self.model, path)
+
+ self.assertEqual(h5node.physical_filename, h5node.local_filename)
+ self.assertIn("base.h5", h5node.physical_filename)
+ self.assertEqual(h5node.physical_basename, "not_exists")
+ self.assertEqual(h5node.physical_name, "/group/not_exists")
+ self.assertEqual(h5node.local_basename, "soft_broken_link")
+ self.assertEqual(h5node.local_name, "/broken_link/soft_broken_link")
+
+ def testSoftLinkToBrokenLink(self):
+ path = ["base.h5", "broken_link", "soft_link_to_broken_link"]
+ h5node = self.getH5NodeFromPath(self.model, path)
+
+ self.assertEqual(h5node.physical_filename, h5node.local_filename)
+ self.assertIn("base.h5", h5node.physical_filename)
+ self.assertEqual(h5node.physical_basename, "not_exists")
+ self.assertEqual(h5node.physical_name, "/group/not_exists")
+ self.assertEqual(h5node.local_basename, "soft_link_to_broken_link")
+ self.assertEqual(h5node.local_name, "/broken_link/soft_link_to_broken_link")
+
+ def testDatasetFromSoftLinkToGroup(self):
+ path = ["base.h5", "link", "soft_link_to_group", "dataset"]
+ h5node = self.getH5NodeFromPath(self.model, path)
+
+ self.assertEqual(h5node.physical_filename, h5node.local_filename)
+ self.assertIn("base.h5", h5node.physical_filename)
+ self.assertEqual(h5node.physical_basename, "dataset")
+ self.assertEqual(h5node.physical_name, "/group/dataset")
+ self.assertEqual(h5node.local_basename, "dataset")
+ self.assertEqual(h5node.local_name, "/link/soft_link_to_group/dataset")
+
+ def testDatasetFromSoftLinkToFile(self):
+ path = ["base.h5", "link", "soft_link_to_file", "link", "soft_link_to_group", "dataset"]
+ h5node = self.getH5NodeFromPath(self.model, path)
+
+ self.assertEqual(h5node.physical_filename, h5node.local_filename)
+ self.assertIn("base.h5", h5node.physical_filename)
+ self.assertEqual(h5node.physical_basename, "dataset")
+ self.assertEqual(h5node.physical_name, "/group/dataset")
+ self.assertEqual(h5node.local_basename, "dataset")
+ self.assertEqual(h5node.local_name, "/link/soft_link_to_file/link/soft_link_to_group/dataset")
+
+
+class TestHdf5TreeView(TestCaseQt):
"""Test to check that icons module."""
def setUp(self):
- super(TestHdf5, self).setUp()
+ super(TestHdf5TreeView, self).setUp()
if h5py is None:
self.skipTest("h5py is not available")
@@ -464,15 +696,147 @@ class TestHdf5(TestCaseQt):
view = hdf5.Hdf5TreeView()
view._createContextMenu(qt.QPoint(0, 0))
+ def testSelection_Simple(self):
+ tree = commonh5.File("/foo/bar/1.mock", "w")
+ item = tree.create_group("a/b/c/d")
+ item.create_group("e").create_group("f")
+
+ model = hdf5.Hdf5TreeModel()
+ model.insertH5pyObject(tree)
+ view = hdf5.Hdf5TreeView()
+ view.setModel(model)
+ view.setSelectedH5Node(item)
+
+ selected = list(view.selectedH5Nodes())[0]
+ self.assertIs(item, selected.h5py_object)
+
+ def testSelection_NotFound(self):
+ tree2 = commonh5.File("/foo/bar/2.mock", "w")
+ tree = commonh5.File("/foo/bar/1.mock", "w")
+ item = tree.create_group("a/b/c/d")
+ item.create_group("e").create_group("f")
+
+ model = hdf5.Hdf5TreeModel()
+ model.insertH5pyObject(tree)
+ view = hdf5.Hdf5TreeView()
+ view.setModel(model)
+ view.setSelectedH5Node(tree2)
+
+ selection = list(view.selectedH5Nodes())
+ self.assertEqual(len(selection), 0)
+
+ def testSelection_ManyGroupFromSameFile(self):
+ tree = commonh5.File("/foo/bar/1.mock", "w")
+ group1 = tree.create_group("a1")
+ group2 = tree.create_group("a2")
+ group3 = tree.create_group("a3")
+ group1.create_group("b/c/d")
+ item = group2.create_group("b/c/d")
+ group3.create_group("b/c/d")
+
+ model = hdf5.Hdf5TreeModel()
+ model.insertH5pyObject(group1)
+ model.insertH5pyObject(group2)
+ model.insertH5pyObject(group3)
+ view = hdf5.Hdf5TreeView()
+ view.setModel(model)
+ view.setSelectedH5Node(item)
+
+ selected = list(view.selectedH5Nodes())[0]
+ self.assertIs(item, selected.h5py_object)
+
+ def testSelection_RootFromSubTree(self):
+ tree = commonh5.File("/foo/bar/1.mock", "w")
+ group = tree.create_group("a1")
+ group.create_group("b/c/d")
+
+ model = hdf5.Hdf5TreeModel()
+ model.insertH5pyObject(group)
+ view = hdf5.Hdf5TreeView()
+ view.setModel(model)
+ view.setSelectedH5Node(group)
+
+ selected = list(view.selectedH5Nodes())[0]
+ self.assertIs(group, selected.h5py_object)
+
+ def testSelection_FileFromSubTree(self):
+ tree = commonh5.File("/foo/bar/1.mock", "w")
+ group = tree.create_group("a1")
+ group.create_group("b").create_group("b").create_group("d")
+
+ model = hdf5.Hdf5TreeModel()
+ model.insertH5pyObject(group)
+ view = hdf5.Hdf5TreeView()
+ view.setModel(model)
+ view.setSelectedH5Node(tree)
+
+ selection = list(view.selectedH5Nodes())
+ self.assertEquals(len(selection), 0)
+
+ def testSelection_Tree(self):
+ tree1 = commonh5.File("/foo/bar/1.mock", "w")
+ tree2 = commonh5.File("/foo/bar/2.mock", "w")
+ tree3 = commonh5.File("/foo/bar/3.mock", "w")
+ tree1.create_group("a/b/c")
+ tree2.create_group("a/b/c")
+ tree3.create_group("a/b/c")
+ item = tree2
+
+ model = hdf5.Hdf5TreeModel()
+ model.insertH5pyObject(tree1)
+ model.insertH5pyObject(tree2)
+ model.insertH5pyObject(tree3)
+ view = hdf5.Hdf5TreeView()
+ view.setModel(model)
+ view.setSelectedH5Node(item)
+
+ selected = list(view.selectedH5Nodes())[0]
+ self.assertIs(item, selected.h5py_object)
+
+ def testSelection_RecurssiveLink(self):
+ """
+ Recurssive link selection
+
+ This example is not really working as expected cause commonh5 do not
+ support recurssive links.
+ But item.name == "/a/b" and the result is found.
+ """
+ tree = commonh5.File("/foo/bar/1.mock", "w")
+ group = tree.create_group("a")
+ group.add_node(commonh5.SoftLink("b", "/"))
+
+ item = tree["/a/b/a/b/a/b/a/b/a/b/a/b/a/b/a/b"]
+
+ model = hdf5.Hdf5TreeModel()
+ model.insertH5pyObject(tree)
+ view = hdf5.Hdf5TreeView()
+ view.setModel(model)
+ view.setSelectedH5Node(item)
+
+ selected = list(view.selectedH5Nodes())[0]
+ self.assertEqual(item.name, selected.h5py_object.name)
+
+ def testSelection_SelectNone(self):
+ tree = commonh5.File("/foo/bar/1.mock", "w")
+
+ model = hdf5.Hdf5TreeModel()
+ model.insertH5pyObject(tree)
+ view = hdf5.Hdf5TreeView()
+ view.setModel(model)
+ view.setSelectedH5Node(tree)
+ view.setSelectedH5Node(None)
+
+ selection = list(view.selectedH5Nodes())
+ self.assertEqual(len(selection), 0)
+
def suite():
test_suite = unittest.TestSuite()
- test_suite.addTest(
- unittest.defaultTestLoader.loadTestsFromTestCase(TestHdf5TreeModel))
- test_suite.addTest(
- unittest.defaultTestLoader.loadTestsFromTestCase(TestNexusSortFilterProxyModel))
- test_suite.addTest(
- unittest.defaultTestLoader.loadTestsFromTestCase(TestHdf5))
+ loadTests = unittest.defaultTestLoader.loadTestsFromTestCase
+ test_suite.addTest(loadTests(TestHdf5TreeModel))
+ test_suite.addTest(loadTests(TestNexusSortFilterProxyModel))
+ test_suite.addTest(loadTests(TestHdf5TreeView))
+ test_suite.addTest(loadTests(TestH5Node))
return test_suite
diff --git a/silx/gui/icons.py b/silx/gui/icons.py
index eaf83b8..07654c1 100644
--- a/silx/gui/icons.py
+++ b/silx/gui/icons.py
@@ -29,15 +29,16 @@ Use :func:`getQIcon` to create Qt QIcon from the name identifying an icon.
__authors__ = ["T. Vincent"]
__license__ = "MIT"
-__date__ = "25/04/2017"
+__date__ = "06/09/2017"
+import os
import logging
import weakref
from . import qt
-from silx.resources import resource_filename
+import silx.resources
from silx.utils import weakref as silxweakref
-from silx.utils.decorators import deprecated
+from silx.utils.deprecation import deprecated
_logger = logging.getLogger(__name__)
@@ -192,7 +193,7 @@ class MultiImageAnimatedIcon(AbstractAnimatedIcon):
self.__frames = []
for i in range(100):
try:
- pixmap = getQPixmap("animated/%s-%02d" % (filename, i))
+ pixmap = getQPixmap("%s/%02d" % (filename, i))
except ValueError:
break
icon = qt.QIcon(pixmap)
@@ -258,13 +259,22 @@ def getWaitIcon():
def getAnimatedIcon(name):
- """Create an AbstractAnimatedIcon from a name.
+ """Create an AbstractAnimatedIcon from a resource name.
+
+ The resource name can be prefixed by the name of a resource directory. For
+ example "silx:foo.png" identify the resource "foo.png" from the resource
+ directory "silx".
+
+ If no prefix are specified, the file with be returned from the silx
+ resource directory with a specific path "gui/icons".
+
+ See also :func:`silx.resources.register_resource_directory`.
Try to load a mng or a gif file, then try to load a multi-image animated
icon.
- In Qt5 mng or gif are not used. It does not take care very well of the
- transparency.
+ In Qt5 mng or gif are not used, because the transparency is not very well
+ managed.
:param str name: Name of the icon, in one of the defined icons
in this module.
@@ -302,6 +312,15 @@ def getAnimatedIcon(name):
def getQIcon(name):
"""Create a QIcon from its name.
+ The resource name can be prefixed by the name of a resource directory. For
+ example "silx:foo.png" identify the resource "foo.png" from the resource
+ directory "silx".
+
+ If no prefix are specified, the file with be returned from the silx
+ resource directory with a specific path "gui/icons".
+
+ See also :func:`silx.resources.register_resource_directory`.
+
:param str name: Name of the icon, in one of the defined icons
in this module.
:return: Corresponding QIcon
@@ -319,6 +338,15 @@ def getQIcon(name):
def getQPixmap(name):
"""Create a QPixmap from its name.
+ The resource name can be prefixed by the name of a resource directory. For
+ example "silx:foo.png" identify the resource "foo.png" from the resource
+ directory "silx".
+
+ If no prefix are specified, the file with be returned from the silx
+ resource directory with a specific path "gui/icons".
+
+ See also :func:`silx.resources.register_resource_directory`.
+
:param str name: Name of the icon, in one of the defined icons
in this module.
:return: Corresponding QPixmap
@@ -332,6 +360,15 @@ def getQFile(name):
"""Create a QFile from an icon name. Filename is found
according to supported Qt formats.
+ The resource name can be prefixed by the name of a resource directory. For
+ example "silx:foo.png" identify the resource "foo.png" from the resource
+ directory "silx".
+
+ If no prefix are specified, the file with be returned from the silx
+ resource directory with a specific path "gui/icons".
+
+ See also :func:`silx.resources.register_resource_directory`.
+
:param str name: Name of the icon, in one of the defined icons
in this module.
:return: Corresponding QFile
@@ -353,7 +390,8 @@ def getQFile(name):
for format_ in _supported_formats:
format_ = str(format_)
- filename = resource_filename('gui/icons/%s.%s' % (name, format_))
+ filename = silx.resources._resource_filename('%s.%s' % (name, format_),
+ default_directory=os.path.join('gui', 'icons'))
qfile = qt.QFile(filename)
if qfile.exists():
return qfile
diff --git a/silx/gui/plot/ColorBar.py b/silx/gui/plot/ColorBar.py
index 93e3c36..8f4bde2 100644
--- a/silx/gui/plot/ColorBar.py
+++ b/silx/gui/plot/ColorBar.py
@@ -33,11 +33,8 @@ __date__ = "11/04/2017"
import logging
import numpy
from ._utils import ticklayout
-from ._utils import clipColormapLogRange
-
-
-from .. import qt
-from silx.gui.plot import Colors
+from .. import qt, icons
+from silx.gui.plot import Colormap
_logger = logging.getLogger(__name__)
@@ -66,12 +63,17 @@ class ColorBarWidget(qt.QWidget):
:param parent: See :class:`QWidget`
:param plot: PlotWidget the colorbar is attached to (optional)
- :param str legend: the label to set to the colormap
+ :param str legend: the label to set to the colorbar
"""
def __init__(self, parent=None, plot=None, legend=None):
- super(ColorBarWidget, self).__init__(parent)
+ self._isConnected = False
self._plot = None
+ self._viewAction = None
+ self._colormap = None
+ self._data = None
+
+ super(ColorBarWidget, self).__init__(parent)
self.__buildGUI()
self.setLegend(legend)
@@ -90,8 +92,6 @@ class ColorBarWidget(qt.QWidget):
self.layout().addWidget(self.legend)
self.layout().setSizeConstraint(qt.QLayout.SetMinAndMaxSize)
- self.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Expanding)
- self.layout().setContentsMargins(0, 0, 0, 0)
def getPlot(self):
"""Returns the :class:`Plot` associated to this widget or None"""
@@ -100,46 +100,75 @@ class ColorBarWidget(qt.QWidget):
def setPlot(self, plot):
"""Associate a plot to the ColorBar
- :param plot: the plot to associate with the colorbar. If None will remove
- any connection with a previous plot.
+ :param plot: the plot to associate with the colorbar.
+ If None will remove any connection with a previous plot.
"""
- # removing previous plot if any
- if self._plot is not None:
- self._plot.sigActiveImageChanged.disconnect(self._activeImageChanged)
-
- # setting the new plot
+ self._disconnectPlot()
self._plot = plot
- if self._plot is not None:
+ self._connectPlot()
+
+ def _disconnectPlot(self):
+ """Disconnect from Plot signals"""
+ if self._plot is not None and self._isConnected:
+ self._isConnected = False
+ self._plot.sigActiveImageChanged.disconnect(
+ self._activeImageChanged)
+ self._plot.sigPlotSignal.disconnect(self._defaultColormapChanged)
+
+ def _connectPlot(self):
+ """Connect to Plot signals"""
+ if self._plot is not None and not self._isConnected:
+ activeImageLegend = self._plot.getActiveImage(just_legend=True)
+ if activeImageLegend is None: # Show plot default colormap
+ self._syncWithDefaultColormap()
+ else: # Show active image colormap
+ self._activeImageChanged(None, activeImageLegend)
self._plot.sigActiveImageChanged.connect(self._activeImageChanged)
- self._activeImageChanged(self._plot.getActiveImage(just_legend=True))
+ self._plot.sigPlotSignal.connect(self._defaultColormapChanged)
+ self._isConnected = True
+
+ def showEvent(self, event):
+ self._connectPlot()
+ if self._viewAction is not None:
+ self._viewAction.setChecked(True)
+
+ def hideEvent(self, event):
+ self._disconnectPlot()
+ if self._viewAction is not None:
+ self._viewAction.setChecked(False)
def getColormap(self):
- """Return the colormap displayed in the colorbar as a dict.
+ """
+
+ :return: the :class:`.Colormap` colormap displayed in the colorbar.
- It returns None if no colormap is set.
- See :class:`silx.gui.plot.Plot` documentation for t