summaryrefslogtreecommitdiff
path: root/silx
diff options
context:
space:
mode:
Diffstat (limited to 'silx')
-rw-r--r--silx/__init__.py61
-rw-r--r--silx/__main__.py75
-rw-r--r--silx/_config.py148
-rw-r--r--silx/app/__init__.py29
-rw-r--r--silx/app/convert.py525
-rw-r--r--silx/app/setup.py41
-rw-r--r--silx/app/test/__init__.py39
-rw-r--r--silx/app/test/test_convert.py167
-rw-r--r--silx/app/test_.py159
-rw-r--r--silx/app/view/About.py257
-rw-r--r--silx/app/view/ApplicationContext.py194
-rw-r--r--silx/app/view/CustomNxdataWidget.py1008
-rw-r--r--silx/app/view/DataPanel.py192
-rw-r--r--silx/app/view/Viewer.py971
-rw-r--r--silx/app/view/__init__.py28
-rw-r--r--silx/app/view/main.py171
-rw-r--r--silx/app/view/setup.py40
-rw-r--r--silx/app/view/test/__init__.py41
-rw-r--r--silx/app/view/test/test_launcher.py151
-rw-r--r--silx/app/view/test/test_view.py394
-rw-r--r--silx/app/view/utils.py45
-rw-r--r--silx/gui/__init__.py49
-rw-r--r--silx/gui/_glutils/Context.py75
-rw-r--r--silx/gui/_glutils/FramebufferTexture.py165
-rw-r--r--silx/gui/_glutils/OpenGLWidget.py423
-rw-r--r--silx/gui/_glutils/Program.py202
-rw-r--r--silx/gui/_glutils/Texture.py352
-rw-r--r--silx/gui/_glutils/VertexBuffer.py266
-rw-r--r--silx/gui/_glutils/__init__.py43
-rw-r--r--silx/gui/_glutils/font.py163
-rw-r--r--silx/gui/_glutils/gl.py168
-rw-r--r--silx/gui/_glutils/utils.py121
-rwxr-xr-xsilx/gui/colors.py1326
-rw-r--r--silx/gui/console.py202
-rw-r--r--silx/gui/data/ArrayTableModel.py670
-rw-r--r--silx/gui/data/ArrayTableWidget.py492
-rw-r--r--silx/gui/data/DataViewer.py593
-rw-r--r--silx/gui/data/DataViewerFrame.py217
-rw-r--r--silx/gui/data/DataViewerSelector.py175
-rw-r--r--silx/gui/data/DataViews.py2059
-rw-r--r--silx/gui/data/Hdf5TableView.py646
-rw-r--r--silx/gui/data/HexaTableView.py286
-rw-r--r--silx/gui/data/NXdataWidgets.py1081
-rw-r--r--silx/gui/data/NumpyAxesSelector.py578
-rw-r--r--silx/gui/data/RecordTableView.py447
-rw-r--r--silx/gui/data/TextFormatter.py395
-rw-r--r--silx/gui/data/_RecordPlot.py92
-rw-r--r--silx/gui/data/_VolumeWindow.py148
-rw-r--r--silx/gui/data/__init__.py35
-rw-r--r--silx/gui/data/setup.py41
-rw-r--r--silx/gui/data/test/__init__.py45
-rw-r--r--silx/gui/data/test/test_arraywidget.py329
-rw-r--r--silx/gui/data/test/test_dataviewer.py314
-rw-r--r--silx/gui/data/test/test_numpyaxesselector.py161
-rw-r--r--silx/gui/data/test/test_textformatter.py212
-rw-r--r--silx/gui/dialog/AbstractDataFileDialog.py1742
-rw-r--r--silx/gui/dialog/ColormapDialog.py1771
-rw-r--r--silx/gui/dialog/DataFileDialog.py340
-rw-r--r--silx/gui/dialog/DatasetDialog.py122
-rw-r--r--silx/gui/dialog/FileTypeComboBox.py226
-rw-r--r--silx/gui/dialog/GroupDialog.py230
-rw-r--r--silx/gui/dialog/ImageFileDialog.py354
-rw-r--r--silx/gui/dialog/SafeFileIconProvider.py154
-rw-r--r--silx/gui/dialog/SafeFileSystemModel.py804
-rw-r--r--silx/gui/dialog/__init__.py29
-rw-r--r--silx/gui/dialog/setup.py40
-rw-r--r--silx/gui/dialog/test/__init__.py49
-rw-r--r--silx/gui/dialog/test/test_colormapdialog.py453
-rw-r--r--silx/gui/dialog/test/test_datafiledialog.py939
-rw-r--r--silx/gui/dialog/test/test_imagefiledialog.py784
-rw-r--r--silx/gui/dialog/utils.py106
-rw-r--r--silx/gui/fit/BackgroundWidget.py534
-rw-r--r--silx/gui/fit/FitConfig.py543
-rw-r--r--silx/gui/fit/FitWidget.py739
-rw-r--r--silx/gui/fit/FitWidgets.py559
-rw-r--r--silx/gui/fit/Parameters.py882
-rw-r--r--silx/gui/fit/__init__.py28
-rw-r--r--silx/gui/fit/setup.py43
-rw-r--r--silx/gui/fit/test/__init__.py43
-rw-r--r--silx/gui/fit/test/testBackgroundWidget.py83
-rw-r--r--silx/gui/fit/test/testFitConfig.py95
-rw-r--r--silx/gui/fit/test/testFitWidget.py135
-rw-r--r--silx/gui/hdf5/Hdf5Formatter.py241
-rw-r--r--silx/gui/hdf5/Hdf5HeaderView.py195
-rwxr-xr-xsilx/gui/hdf5/Hdf5Item.py642
-rw-r--r--silx/gui/hdf5/Hdf5LoadingItem.py77
-rw-r--r--silx/gui/hdf5/Hdf5Node.py238
-rw-r--r--silx/gui/hdf5/Hdf5TreeModel.py778
-rw-r--r--silx/gui/hdf5/Hdf5TreeView.py271
-rw-r--r--silx/gui/hdf5/NexusSortFilterProxyModel.py224
-rw-r--r--silx/gui/hdf5/__init__.py44
-rw-r--r--silx/gui/hdf5/_utils.py461
-rw-r--r--silx/gui/hdf5/setup.py41
-rw-r--r--silx/gui/hdf5/test/__init__.py39
-rwxr-xr-xsilx/gui/hdf5/test/test_hdf5.py1140
-rw-r--r--silx/gui/icons.py425
-rw-r--r--silx/gui/plot/AlphaSlider.py300
-rw-r--r--silx/gui/plot/ColorBar.py881
-rw-r--r--silx/gui/plot/Colormap.py42
-rw-r--r--silx/gui/plot/ColormapDialog.py43
-rw-r--r--silx/gui/plot/Colors.py90
-rw-r--r--silx/gui/plot/CompareImages.py1249
-rw-r--r--silx/gui/plot/ComplexImageView.py518
-rw-r--r--silx/gui/plot/CurvesROIWidget.py1584
-rw-r--r--silx/gui/plot/ImageStack.py636
-rw-r--r--silx/gui/plot/ImageView.py854
-rw-r--r--silx/gui/plot/Interaction.py350
-rw-r--r--silx/gui/plot/ItemsSelectionDialog.py286
-rwxr-xr-xsilx/gui/plot/LegendSelector.py1036
-rw-r--r--silx/gui/plot/LimitsHistory.py83
-rw-r--r--silx/gui/plot/MaskToolsWidget.py919
-rw-r--r--silx/gui/plot/PlotActions.py67
-rw-r--r--silx/gui/plot/PlotEvents.py166
-rw-r--r--silx/gui/plot/PlotInteraction.py1748
-rw-r--r--silx/gui/plot/PlotToolButtons.py592
-rw-r--r--silx/gui/plot/PlotTools.py43
-rwxr-xr-xsilx/gui/plot/PlotWidget.py3621
-rw-r--r--silx/gui/plot/PlotWindow.py994
-rw-r--r--silx/gui/plot/PrintPreviewToolButton.py392
-rw-r--r--silx/gui/plot/Profile.py352
-rw-r--r--silx/gui/plot/ProfileMainWindow.py110
-rw-r--r--silx/gui/plot/ROIStatsWidget.py780
-rw-r--r--silx/gui/plot/ScatterMaskToolsWidget.py621
-rw-r--r--silx/gui/plot/ScatterView.py405
-rw-r--r--silx/gui/plot/StackView.py1254
-rw-r--r--silx/gui/plot/StatsWidget.py1661
-rw-r--r--silx/gui/plot/_BaseMaskToolsWidget.py1282
-rw-r--r--silx/gui/plot/__init__.py71
-rw-r--r--silx/gui/plot/_utils/__init__.py93
-rw-r--r--silx/gui/plot/_utils/delaunay.py62
-rw-r--r--silx/gui/plot/_utils/dtime_ticklayout.py442
-rw-r--r--silx/gui/plot/_utils/panzoom.py292
-rw-r--r--silx/gui/plot/_utils/setup.py42
-rw-r--r--silx/gui/plot/_utils/test/__init__.py43
-rw-r--r--silx/gui/plot/_utils/test/test_dtime_ticklayout.py93
-rw-r--r--silx/gui/plot/_utils/test/test_ticklayout.py92
-rw-r--r--silx/gui/plot/_utils/ticklayout.py267
-rw-r--r--silx/gui/plot/actions/PlotAction.py78
-rw-r--r--silx/gui/plot/actions/PlotToolAction.py150
-rw-r--r--silx/gui/plot/actions/__init__.py42
-rwxr-xr-xsilx/gui/plot/actions/control.py694
-rw-r--r--silx/gui/plot/actions/fit.py403
-rw-r--r--silx/gui/plot/actions/histogram.py392
-rw-r--r--silx/gui/plot/actions/io.py818
-rw-r--r--silx/gui/plot/actions/medfilt.py147
-rw-r--r--silx/gui/plot/actions/mode.py104
-rwxr-xr-xsilx/gui/plot/backends/BackendBase.py578
-rwxr-xr-xsilx/gui/plot/backends/BackendMatplotlib.py1544
-rwxr-xr-xsilx/gui/plot/backends/BackendOpenGL.py1420
-rw-r--r--silx/gui/plot/backends/__init__.py29
-rw-r--r--silx/gui/plot/backends/glutils/GLPlotCurve.py1375
-rw-r--r--silx/gui/plot/backends/glutils/GLPlotFrame.py1219
-rw-r--r--silx/gui/plot/backends/glutils/GLPlotImage.py756
-rw-r--r--silx/gui/plot/backends/glutils/GLPlotItem.py99
-rw-r--r--silx/gui/plot/backends/glutils/GLPlotTriangles.py197
-rw-r--r--silx/gui/plot/backends/glutils/GLSupport.py158
-rw-r--r--silx/gui/plot/backends/glutils/GLText.py287
-rw-r--r--silx/gui/plot/backends/glutils/GLTexture.py241
-rw-r--r--silx/gui/plot/backends/glutils/PlotImageFile.py153
-rw-r--r--silx/gui/plot/backends/glutils/__init__.py46
-rw-r--r--silx/gui/plot/items/__init__.py52
-rw-r--r--silx/gui/plot/items/_arc_roi.py878
-rw-r--r--silx/gui/plot/items/_pick.py72
-rw-r--r--silx/gui/plot/items/_roi_base.py835
-rw-r--r--silx/gui/plot/items/axis.py569
-rw-r--r--silx/gui/plot/items/complex.py386
-rw-r--r--silx/gui/plot/items/core.py1734
-rw-r--r--silx/gui/plot/items/curve.py326
-rw-r--r--silx/gui/plot/items/histogram.py389
-rw-r--r--silx/gui/plot/items/image.py617
-rwxr-xr-xsilx/gui/plot/items/marker.py281
-rw-r--r--silx/gui/plot/items/roi.py1519
-rw-r--r--silx/gui/plot/items/scatter.py973
-rw-r--r--silx/gui/plot/items/shape.py288
-rw-r--r--silx/gui/plot/matplotlib/Colormap.py249
-rw-r--r--silx/gui/plot/matplotlib/__init__.py37
-rw-r--r--silx/gui/plot/setup.py54
-rw-r--r--silx/gui/plot/stats/__init__.py33
-rw-r--r--silx/gui/plot/stats/stats.py890
-rw-r--r--silx/gui/plot/stats/statshandler.py202
-rw-r--r--silx/gui/plot/test/__init__.py92
-rw-r--r--silx/gui/plot/test/testAlphaSlider.py218
-rw-r--r--silx/gui/plot/test/testColorBar.py354
-rw-r--r--silx/gui/plot/test/testCompareImages.py117
-rw-r--r--silx/gui/plot/test/testComplexImageView.py95
-rw-r--r--silx/gui/plot/test/testCurvesROIWidget.py469
-rw-r--r--silx/gui/plot/test/testImageStack.py197
-rw-r--r--silx/gui/plot/test/testImageView.py136
-rw-r--r--silx/gui/plot/test/testInteraction.py89
-rw-r--r--silx/gui/plot/test/testItem.py340
-rw-r--r--silx/gui/plot/test/testLegendSelector.py142
-rw-r--r--silx/gui/plot/test/testLimitConstraints.py125
-rw-r--r--silx/gui/plot/test/testMaskToolsWidget.py316
-rw-r--r--silx/gui/plot/test/testPixelIntensityHistoAction.py157
-rw-r--r--silx/gui/plot/test/testPlotInteraction.py172
-rwxr-xr-xsilx/gui/plot/test/testPlotWidget.py2072
-rw-r--r--silx/gui/plot/test/testPlotWidgetNoBackend.py631
-rw-r--r--silx/gui/plot/test/testPlotWindow.py185
-rw-r--r--silx/gui/plot/test/testRoiStatsWidget.py290
-rw-r--r--silx/gui/plot/test/testSaveAction.py143
-rw-r--r--silx/gui/plot/test/testScatterMaskToolsWidget.py318
-rw-r--r--silx/gui/plot/test/testScatterView.py134
-rw-r--r--silx/gui/plot/test/testStackView.py261
-rw-r--r--silx/gui/plot/test/testStats.py1058
-rw-r--r--silx/gui/plot/test/testUtilsAxis.py214
-rw-r--r--silx/gui/plot/test/utils.py94
-rw-r--r--silx/gui/plot/tools/CurveLegendsWidget.py247
-rw-r--r--silx/gui/plot/tools/LimitsToolBar.py131
-rw-r--r--silx/gui/plot/tools/PositionInfo.py376
-rw-r--r--silx/gui/plot/tools/RadarView.py361
-rw-r--r--silx/gui/plot/tools/__init__.py50
-rw-r--r--silx/gui/plot/tools/profile/ScatterProfileToolBar.py54
-rw-r--r--silx/gui/plot/tools/profile/__init__.py38
-rw-r--r--silx/gui/plot/tools/profile/core.py525
-rw-r--r--silx/gui/plot/tools/profile/editors.py307
-rw-r--r--silx/gui/plot/tools/profile/manager.py1076
-rw-r--r--silx/gui/plot/tools/profile/rois.py1156
-rw-r--r--silx/gui/plot/tools/profile/toolbar.py172
-rw-r--r--silx/gui/plot/tools/roi.py1417
-rw-r--r--silx/gui/plot/tools/test/__init__.py52
-rw-r--r--silx/gui/plot/tools/test/testCurveLegendsWidget.py125
-rw-r--r--silx/gui/plot/tools/test/testProfile.py673
-rw-r--r--silx/gui/plot/tools/test/testROI.py694
-rw-r--r--silx/gui/plot/tools/test/testScatterProfileToolBar.py196
-rw-r--r--silx/gui/plot/tools/test/testTools.py147
-rw-r--r--silx/gui/plot/tools/toolbars.py362
-rw-r--r--silx/gui/plot/utils/__init__.py30
-rw-r--r--silx/gui/plot/utils/axis.py403
-rw-r--r--silx/gui/plot/utils/intersections.py101
-rw-r--r--silx/gui/plot3d/ParamTreeView.py546
-rw-r--r--silx/gui/plot3d/Plot3DWidget.py460
-rw-r--r--silx/gui/plot3d/Plot3DWindow.py88
-rw-r--r--silx/gui/plot3d/SFViewParamTree.py1817
-rw-r--r--silx/gui/plot3d/ScalarFieldView.py1552
-rw-r--r--silx/gui/plot3d/SceneWidget.py687
-rw-r--r--silx/gui/plot3d/SceneWindow.py219
-rw-r--r--silx/gui/plot3d/__init__.py40
-rw-r--r--silx/gui/plot3d/_model/__init__.py35
-rw-r--r--silx/gui/plot3d/_model/core.py372
-rw-r--r--silx/gui/plot3d/_model/items.py1760
-rw-r--r--silx/gui/plot3d/_model/model.py184
-rw-r--r--silx/gui/plot3d/actions/Plot3DAction.py71
-rw-r--r--silx/gui/plot3d/actions/__init__.py34
-rw-r--r--silx/gui/plot3d/actions/io.py336
-rw-r--r--silx/gui/plot3d/actions/mode.py178
-rw-r--r--silx/gui/plot3d/actions/viewpoint.py231
-rw-r--r--silx/gui/plot3d/items/__init__.py43
-rw-r--r--silx/gui/plot3d/items/_pick.py265
-rw-r--r--silx/gui/plot3d/items/clipplane.py136
-rw-r--r--silx/gui/plot3d/items/core.py779
-rw-r--r--silx/gui/plot3d/items/image.py425
-rw-r--r--silx/gui/plot3d/items/mesh.py792
-rw-r--r--silx/gui/plot3d/items/mixins.py288
-rw-r--r--silx/gui/plot3d/items/scatter.py617
-rw-r--r--silx/gui/plot3d/items/volume.py886
-rw-r--r--silx/gui/plot3d/scene/__init__.py34
-rw-r--r--silx/gui/plot3d/scene/axes.py258
-rw-r--r--silx/gui/plot3d/scene/camera.py353
-rw-r--r--silx/gui/plot3d/scene/core.py343
-rw-r--r--silx/gui/plot3d/scene/cutplane.py390
-rw-r--r--silx/gui/plot3d/scene/event.py225
-rw-r--r--silx/gui/plot3d/scene/function.py654
-rw-r--r--silx/gui/plot3d/scene/interaction.py701
-rw-r--r--silx/gui/plot3d/scene/primitives.py2524
-rw-r--r--silx/gui/plot3d/scene/test/__init__.py43
-rw-r--r--silx/gui/plot3d/scene/test/test_transform.py91
-rw-r--r--silx/gui/plot3d/scene/test/test_utils.py275
-rw-r--r--silx/gui/plot3d/scene/text.py535
-rw-r--r--silx/gui/plot3d/scene/transform.py1027
-rw-r--r--silx/gui/plot3d/scene/utils.py662
-rw-r--r--silx/gui/plot3d/scene/viewport.py603
-rw-r--r--silx/gui/plot3d/scene/window.py430
-rw-r--r--silx/gui/plot3d/setup.py50
-rw-r--r--silx/gui/plot3d/test/__init__.py75
-rw-r--r--silx/gui/plot3d/test/testGL.py84
-rw-r--r--silx/gui/plot3d/test/testScalarFieldView.py139
-rw-r--r--silx/gui/plot3d/test/testSceneWidget.py84
-rw-r--r--silx/gui/plot3d/test/testSceneWidgetPicking.py326
-rw-r--r--silx/gui/plot3d/test/testSceneWindow.py245
-rw-r--r--silx/gui/plot3d/test/testStatsWidget.py216
-rw-r--r--silx/gui/plot3d/tools/GroupPropertiesWidget.py202
-rw-r--r--silx/gui/plot3d/tools/PositionInfoWidget.py219
-rw-r--r--silx/gui/plot3d/tools/ViewpointTools.py84
-rw-r--r--silx/gui/plot3d/tools/__init__.py34
-rw-r--r--silx/gui/plot3d/tools/test/__init__.py41
-rw-r--r--silx/gui/plot3d/tools/test/testPositionInfoWidget.py101
-rw-r--r--silx/gui/plot3d/tools/toolbars.py209
-rw-r--r--silx/gui/plot3d/utils/__init__.py28
-rw-r--r--silx/gui/plot3d/utils/mng.py121
-rw-r--r--silx/gui/printer.py62
-rw-r--r--silx/gui/qt/__init__.py60
-rw-r--r--silx/gui/qt/_macosx.py68
-rw-r--r--silx/gui/qt/_pyside_dynamic.py239
-rw-r--r--silx/gui/qt/_pyside_missing.py274
-rw-r--r--silx/gui/qt/_qt.py289
-rw-r--r--silx/gui/qt/_utils.py71
-rw-r--r--silx/gui/qt/inspect.py87
-rw-r--r--silx/gui/setup.py55
-rw-r--r--silx/gui/test/__init__.py113
-rwxr-xr-xsilx/gui/test/test_colors.py619
-rw-r--r--silx/gui/test/test_console.py91
-rw-r--r--silx/gui/test/test_icons.py158
-rw-r--r--silx/gui/test/test_qt.py201
-rw-r--r--silx/gui/test/utils.py43
-rwxr-xr-xsilx/gui/utils/__init__.py76
-rw-r--r--silx/gui/utils/concurrent.py105
-rw-r--r--silx/gui/utils/glutils/__init__.py199
-rw-r--r--silx/gui/utils/image.py143
-rw-r--r--silx/gui/utils/matplotlib.py71
-rw-r--r--silx/gui/utils/projecturl.py77
-rwxr-xr-xsilx/gui/utils/qtutils.py196
-rw-r--r--silx/gui/utils/signal.py141
-rwxr-xr-xsilx/gui/utils/test/__init__.py56
-rw-r--r--silx/gui/utils/test/test.py76
-rw-r--r--silx/gui/utils/test/test_async.py138
-rw-r--r--silx/gui/utils/test/test_glutils.py66
-rw-r--r--silx/gui/utils/test/test_image.py90
-rwxr-xr-xsilx/gui/utils/test/test_qtutils.py75
-rw-r--r--silx/gui/utils/test/test_testutils.py55
-rw-r--r--silx/gui/utils/testutils.py518
-rw-r--r--silx/gui/widgets/BoxLayoutDockWidget.py90
-rw-r--r--silx/gui/widgets/ColormapNameComboBox.py166
-rw-r--r--silx/gui/widgets/ElidedLabel.py137
-rw-r--r--silx/gui/widgets/FloatEdit.py65
-rw-r--r--silx/gui/widgets/FlowLayout.py177
-rw-r--r--silx/gui/widgets/FrameBrowser.py324
-rw-r--r--silx/gui/widgets/HierarchicalTableView.py172
-rwxr-xr-xsilx/gui/widgets/LegendIconWidget.py514
-rw-r--r--silx/gui/widgets/MedianFilterDialog.py80
-rw-r--r--silx/gui/widgets/MultiModeAction.py83
-rw-r--r--silx/gui/widgets/PeriodicTable.py831
-rw-r--r--silx/gui/widgets/PrintGeometryDialog.py222
-rw-r--r--silx/gui/widgets/PrintPreview.py728
-rw-r--r--silx/gui/widgets/RangeSlider.py765
-rw-r--r--silx/gui/widgets/TableWidget.py626
-rw-r--r--silx/gui/widgets/ThreadPoolPushButton.py238
-rw-r--r--silx/gui/widgets/UrlSelectionTable.py172
-rw-r--r--silx/gui/widgets/WaitingPushButton.py245
-rw-r--r--silx/gui/widgets/__init__.py27
-rw-r--r--silx/gui/widgets/setup.py41
-rw-r--r--silx/gui/widgets/test/__init__.py59
-rw-r--r--silx/gui/widgets/test/test_boxlayoutdockwidget.py83
-rw-r--r--silx/gui/widgets/test/test_elidedlabel.py111
-rw-r--r--silx/gui/widgets/test/test_flowlayout.py77
-rw-r--r--silx/gui/widgets/test/test_framebrowser.py73
-rw-r--r--silx/gui/widgets/test/test_hierarchicaltableview.py117
-rw-r--r--silx/gui/widgets/test/test_legendiconwidget.py74
-rw-r--r--silx/gui/widgets/test/test_periodictable.py163
-rw-r--r--silx/gui/widgets/test/test_printpreview.py74
-rw-r--r--silx/gui/widgets/test/test_rangeslider.py114
-rw-r--r--silx/gui/widgets/test/test_tablewidget.py61
-rw-r--r--silx/gui/widgets/test/test_threadpoolpushbutton.py135
-rw-r--r--silx/image/__init__.py33
-rw-r--r--silx/image/_boundingbox.py100
-rw-r--r--silx/image/backprojection.py25
-rw-r--r--silx/image/bilinear.pyx465
-rw-r--r--silx/image/marchingsquares/__init__.py117
-rw-r--r--silx/image/marchingsquares/_mergeimpl.pyx1319
-rw-r--r--silx/image/marchingsquares/_skimage.py139
-rw-r--r--silx/image/marchingsquares/include/patterns.h89
-rw-r--r--silx/image/marchingsquares/setup.py51
-rw-r--r--silx/image/marchingsquares/test/__init__.py40
-rw-r--r--silx/image/marchingsquares/test/test_funcapi.py99
-rw-r--r--silx/image/marchingsquares/test/test_mergeimpl.py272
-rw-r--r--silx/image/medianfilter.py114
-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/setup.py47
-rw-r--r--silx/image/shapes.pyx321
-rw-r--r--silx/image/sift.py25
-rw-r--r--silx/image/test/__init__.py48
-rw-r--r--silx/image/test/test_bb.py86
-rw-r--r--silx/image/test/test_bilinear.py178
-rw-r--r--silx/image/test/test_medianfilter.py76
-rw-r--r--silx/image/test/test_shapes.py366
-rw-r--r--silx/image/test/test_tomography.py66
-rw-r--r--silx/image/tomography.py301
-rw-r--r--silx/image/utils.py53
-rw-r--r--silx/io/__init__.py51
-rw-r--r--silx/io/commonh5.py1083
-rw-r--r--silx/io/configdict.py540
-rw-r--r--silx/io/convert.py343
-rw-r--r--silx/io/dictdump.py842
-rwxr-xr-xsilx/io/fabioh5.py1051
-rw-r--r--silx/io/h5py_utils.py317
-rw-r--r--silx/io/nxdata/__init__.py66
-rw-r--r--silx/io/nxdata/_utils.py184
-rw-r--r--silx/io/nxdata/parse.py997
-rw-r--r--silx/io/nxdata/write.py203
-rw-r--r--silx/io/octaveh5.py171
-rw-r--r--silx/io/rawh5.py71
-rw-r--r--silx/io/setup.py87
-rw-r--r--silx/io/specfile.pyx1268
-rw-r--r--silx/io/specfile/include/Lists.h56
-rw-r--r--silx/io/specfile/include/SpecFile.h297
-rw-r--r--silx/io/specfile/include/SpecFileCython.h28
-rw-r--r--silx/io/specfile/include/SpecFileP.h79
-rw-r--r--silx/io/specfile/include/locale_management.h28
-rw-r--r--silx/io/specfile/src/locale_management.c79
-rw-r--r--silx/io/specfile/src/sfdata.c757
-rw-r--r--silx/io/specfile/src/sfheader.c792
-rw-r--r--silx/io/specfile/src/sfindex.c556
-rw-r--r--silx/io/specfile/src/sfinit.c830
-rw-r--r--silx/io/specfile/src/sflabel.c654
-rw-r--r--silx/io/specfile/src/sflists.c189
-rw-r--r--silx/io/specfile/src/sfmca.c341
-rw-r--r--silx/io/specfile/src/sftools.c554
-rw-r--r--silx/io/specfile/src/sfwrite.c592
-rw-r--r--silx/io/specfile_wrapper.pxd77
-rw-r--r--silx/io/specfilewrapper.py371
-rw-r--r--silx/io/spech5.py883
-rw-r--r--silx/io/spectoh5.py81
-rw-r--r--silx/io/test/__init__.py61
-rw-r--r--silx/io/test/test_commonh5.py295
-rw-r--r--silx/io/test/test_dictdump.py1025
-rwxr-xr-xsilx/io/test/test_fabioh5.py629
-rw-r--r--silx/io/test/test_h5py_utils.py397
-rw-r--r--silx/io/test/test_nxdata.py579
-rw-r--r--silx/io/test/test_octaveh5.py165
-rw-r--r--silx/io/test/test_rawh5.py96
-rw-r--r--silx/io/test/test_specfile.py433
-rw-r--r--silx/io/test/test_specfilewrapper.py206
-rw-r--r--silx/io/test/test_spech5.py881
-rw-r--r--silx/io/test/test_spectoh5.py194
-rw-r--r--silx/io/test/test_url.py228
-rw-r--r--silx/io/test/test_utils.py888
-rw-r--r--silx/io/url.py390
-rw-r--r--silx/io/utils.py1142
-rw-r--r--silx/math/__init__.py39
-rw-r--r--silx/math/calibration.py180
-rw-r--r--silx/math/chistogramnd.pyx1251
-rw-r--r--silx/math/chistogramnd_lut.pyx435
-rw-r--r--silx/math/colormap.pyx559
-rw-r--r--silx/math/combo.pyx329
-rw-r--r--silx/math/fft/__init__.py8
-rw-r--r--silx/math/fft/basefft.py146
-rw-r--r--silx/math/fft/clfft.py286
-rw-r--r--silx/math/fft/cufft.py253
-rw-r--r--silx/math/fft/fft.py96
-rw-r--r--silx/math/fft/fftw.py214
-rw-r--r--silx/math/fft/npfft.py124
-rw-r--r--silx/math/fft/setup.py41
-rw-r--r--silx/math/fft/test/__init__.py25
-rw-r--r--silx/math/fft/test/test_fft.py270
-rw-r--r--silx/math/fit/__init__.py39
-rw-r--r--silx/math/fit/bgtheories.py440
-rw-r--r--silx/math/fit/filters.pyx416
-rw-r--r--silx/math/fit/filters/include/filters.h45
-rw-r--r--silx/math/fit/filters/src/smoothnd.c317
-rw-r--r--silx/math/fit/filters/src/snip1d.c149
-rw-r--r--silx/math/fit/filters/src/snip2d.c96
-rw-r--r--silx/math/fit/filters/src/snip3d.c186
-rw-r--r--silx/math/fit/filters/src/strip.c118
-rw-r--r--silx/math/fit/filters_wrapper.pxd71
-rw-r--r--silx/math/fit/fitmanager.py1087
-rw-r--r--silx/math/fit/fittheories.py1374
-rw-r--r--silx/math/fit/fittheory.py161
-rw-r--r--silx/math/fit/functions.pyx985
-rw-r--r--silx/math/fit/functions/include/functions.h68
-rw-r--r--silx/math/fit/functions/src/funs.c1265
-rw-r--r--silx/math/fit/functions_wrapper.pxd170
-rw-r--r--silx/math/fit/leastsq.py901
-rw-r--r--silx/math/fit/peaks.pyx175
-rw-r--r--silx/math/fit/peaks/include/peaks.h32
-rw-r--r--silx/math/fit/peaks/src/peaks.c255
-rw-r--r--silx/math/fit/peaks_wrapper.pxd41
-rw-r--r--silx/math/fit/setup.py85
-rw-r--r--silx/math/fit/test/__init__.py46
-rw-r--r--silx/math/fit/test/test_bgtheories.py169
-rw-r--r--silx/math/fit/test/test_filters.py137
-rw-r--r--silx/math/fit/test/test_fit.py387
-rw-r--r--silx/math/fit/test/test_fitmanager.py513
-rw-r--r--silx/math/fit/test/test_functions.py272
-rw-r--r--silx/math/fit/test/test_peaks.py146
-rw-r--r--silx/math/histogram.py593
-rw-r--r--silx/math/histogramnd/include/histogramnd_c.h313
-rw-r--r--silx/math/histogramnd/include/msvc/stdint.h247
-rw-r--r--silx/math/histogramnd/include/templates.h30
-rw-r--r--silx/math/histogramnd/src/histogramnd_c.c301
-rw-r--r--silx/math/histogramnd/src/histogramnd_template.c260
-rw-r--r--silx/math/histogramnd_c.pxd299
-rw-r--r--silx/math/include/math_compatibility.h53
-rw-r--r--silx/math/interpolate.pyx165
-rw-r--r--silx/math/marchingcubes.pyx246
-rw-r--r--silx/math/marchingcubes/mc.hpp724
-rw-r--r--silx/math/marchingcubes/mc_lut.cpp316
-rw-r--r--silx/math/math_compatibility.pxd35
-rw-r--r--silx/math/mc.pxd51
-rw-r--r--silx/math/medianfilter/__init__.py30
-rw-r--r--silx/math/medianfilter/include/median_filter.hpp284
-rw-r--r--silx/math/medianfilter/median_filter.pxd42
-rw-r--r--silx/math/medianfilter/medianfilter.pyx496
-rw-r--r--silx/math/medianfilter/setup.py59
-rw-r--r--silx/math/medianfilter/test/__init__.py36
-rw-r--r--silx/math/medianfilter/test/benchmark.py122
-rw-r--r--silx/math/medianfilter/test/test_medianfilter.py740
-rw-r--r--silx/math/setup.py99
-rw-r--r--silx/math/test/__init__.py58
-rw-r--r--silx/math/test/benchmark_combo.py203
-rw-r--r--silx/math/test/histo_benchmarks.py269
-rw-r--r--silx/math/test/test_HistogramndLut_nominal.py587
-rw-r--r--silx/math/test/test_calibration.py158
-rw-r--r--silx/math/test/test_colormap.py266
-rw-r--r--silx/math/test/test_combo.py218
-rw-r--r--silx/math/test/test_histogramnd_error.py535
-rw-r--r--silx/math/test/test_histogramnd_nominal.py949
-rw-r--r--silx/math/test/test_histogramnd_vs_np.py848
-rw-r--r--silx/math/test/test_interpolate.py136
-rw-r--r--silx/math/test/test_marchingcubes.py188
-rw-r--r--silx/opencl/__init__.py52
-rw-r--r--silx/opencl/backprojection.py397
-rw-r--r--silx/opencl/codec/__init__.py0
-rw-r--r--silx/opencl/codec/byte_offset.py439
-rw-r--r--silx/opencl/codec/setup.py43
-rw-r--r--silx/opencl/codec/test/__init__.py37
-rw-r--r--silx/opencl/codec/test/test_byte_offset.py315
-rw-r--r--silx/opencl/common.py691
-rw-r--r--silx/opencl/convolution.py442
-rw-r--r--silx/opencl/image.py387
-rw-r--r--silx/opencl/linalg.py220
-rw-r--r--silx/opencl/medfilt.py269
-rw-r--r--silx/opencl/processing.py447
-rw-r--r--silx/opencl/projection.py428
-rw-r--r--silx/opencl/reconstruction.py388
-rw-r--r--silx/opencl/setup.py48
-rw-r--r--silx/opencl/sinofilter.py435
-rw-r--r--silx/opencl/sparse.py377
-rw-r--r--silx/opencl/statistics.py242
-rw-r--r--silx/opencl/test/__init__.py68
-rw-r--r--silx/opencl/test/test_addition.py154
-rw-r--r--silx/opencl/test/test_array_utils.py161
-rw-r--r--silx/opencl/test/test_backprojection.py231
-rw-r--r--silx/opencl/test/test_convolution.py265
-rw-r--r--silx/opencl/test/test_doubleword.py258
-rw-r--r--silx/opencl/test/test_image.py137
-rw-r--r--silx/opencl/test/test_kahan.py269
-rw-r--r--silx/opencl/test/test_linalg.py216
-rw-r--r--silx/opencl/test/test_medfilt.py175
-rw-r--r--silx/opencl/test/test_projection.py131
-rw-r--r--silx/opencl/test/test_sparse.py203
-rw-r--r--silx/opencl/test/test_stats.py116
-rw-r--r--silx/opencl/utils.py214
-rw-r--r--silx/resources/__init__.py286
-rw-r--r--silx/resources/gui/colormaps/cividis.npybin3200 -> 0 bytes
-rw-r--r--silx/resources/gui/colormaps/inferno.npybin3152 -> 0 bytes
-rw-r--r--silx/resources/gui/colormaps/magma.npybin3152 -> 0 bytes
-rw-r--r--silx/resources/gui/colormaps/plasma.npybin3152 -> 0 bytes
-rw-r--r--silx/resources/gui/colormaps/viridis.npybin3152 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/3d-plane-normal-x.pngbin743 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/3d-plane-normal-x.svg9
-rw-r--r--silx/resources/gui/icons/3d-plane-normal-y.pngbin791 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/3d-plane-normal-y.svg9
-rw-r--r--silx/resources/gui/icons/3d-plane-normal-z.pngbin681 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/3d-plane-normal-z.svg11
-rw-r--r--silx/resources/gui/icons/3d-plane-pan.pngbin1428 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/3d-plane-pan.svg14
-rw-r--r--silx/resources/gui/icons/3d-plane.pngbin1134 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/3d-plane.svg7
-rw-r--r--silx/resources/gui/icons/add-range-horizontal.pngbin560 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/add-range-horizontal.svg2
-rw-r--r--silx/resources/gui/icons/add-shape-arc.pngbin1164 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/add-shape-arc.svg2
-rw-r--r--silx/resources/gui/icons/add-shape-circle.pngbin1238 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/add-shape-circle.svg2
-rw-r--r--silx/resources/gui/icons/add-shape-cross.pngbin501 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/add-shape-cross.svg2
-rw-r--r--silx/resources/gui/icons/add-shape-diagonal.pngbin626 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/add-shape-diagonal.svg2
-rw-r--r--silx/resources/gui/icons/add-shape-ellipse.pngbin1180 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/add-shape-ellipse.svg2
-rw-r--r--silx/resources/gui/icons/add-shape-horizontal.pngbin408 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/add-shape-horizontal.svg2
-rw-r--r--silx/resources/gui/icons/add-shape-point.pngbin482 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/add-shape-point.svg2
-rw-r--r--silx/resources/gui/icons/add-shape-polygon.pngbin1217 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/add-shape-polygon.svg2
-rw-r--r--silx/resources/gui/icons/add-shape-rectangle.pngbin463 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/add-shape-rectangle.svg2
-rw-r--r--silx/resources/gui/icons/add-shape-unknown.pngbin1506 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/add-shape-unknown.svg2
-rw-r--r--silx/resources/gui/icons/add-shape-vertical.pngbin422 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/add-shape-vertical.svg2
-rw-r--r--silx/resources/gui/icons/add.pngbin470 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/add.svg2
-rw-r--r--silx/resources/gui/icons/arrow-keys.pngbin669 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/arrow-keys.svg3
-rw-r--r--silx/resources/gui/icons/axis.pngbin1740 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/axis.svg2
-rw-r--r--silx/resources/gui/icons/backend-opengl.pngbin1582 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/backend-opengl.svg18
-rw-r--r--silx/resources/gui/icons/camera.pngbin348 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/camera.svg9
-rw-r--r--silx/resources/gui/icons/clipboard.pngbin736 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/clipboard.svg14
-rwxr-xr-xsilx/resources/gui/icons/close.pngbin2243 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/close.svg2
-rw-r--r--silx/resources/gui/icons/colorbar.pngbin657 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/colorbar.svg3
-rw-r--r--silx/resources/gui/icons/colormap-histogram.pngbin641 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/colormap-histogram.svg15
-rw-r--r--silx/resources/gui/icons/colormap-none.pngbin232 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/colormap-none.svg14
-rw-r--r--silx/resources/gui/icons/colormap-norm-arcsinh.pngbin648 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/colormap-norm-arcsinh.svg2
-rw-r--r--silx/resources/gui/icons/colormap-norm-gamma.pngbin994 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/colormap-norm-gamma.svg2
-rw-r--r--silx/resources/gui/icons/colormap-norm-linear.pngbin675 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/colormap-norm-linear.svg2
-rw-r--r--silx/resources/gui/icons/colormap-norm-log.pngbin512 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/colormap-norm-log.svg2
-rw-r--r--silx/resources/gui/icons/colormap-norm-sqrt.pngbin569 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/colormap-norm-sqrt.svg2
-rw-r--r--silx/resources/gui/icons/colormap-range.pngbin284 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/colormap-range.svg15
-rwxr-xr-xsilx/resources/gui/icons/colormap.pngbin1583 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/colormap.svg13
-rw-r--r--silx/resources/gui/icons/compare-align-auto.pngbin1446 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/compare-align-auto.svg4
-rw-r--r--silx/resources/gui/icons/compare-align-center.pngbin716 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/compare-align-center.svg4
-rw-r--r--silx/resources/gui/icons/compare-align-origin.pngbin728 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/compare-align-origin.svg4
-rw-r--r--silx/resources/gui/icons/compare-align-stretch.pngbin903 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/compare-align-stretch.svg4
-rw-r--r--silx/resources/gui/icons/compare-keypoints.pngbin616 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/compare-keypoints.svg17
-rw-r--r--silx/resources/gui/icons/compare-mode-a-minus-b.pngbin3862 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/compare-mode-a-minus-b.svg25
-rw-r--r--silx/resources/gui/icons/compare-mode-a.pngbin803 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/compare-mode-a.svg13
-rw-r--r--silx/resources/gui/icons/compare-mode-b.pngbin740 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/compare-mode-b.svg13
-rw-r--r--silx/resources/gui/icons/compare-mode-hline.pngbin902 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/compare-mode-hline.svg16
-rw-r--r--silx/resources/gui/icons/compare-mode-rb-channel.pngbin1269 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/compare-mode-rb-channel.svg17
-rw-r--r--silx/resources/gui/icons/compare-mode-rbneg-channel.pngbin1260 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/compare-mode-rbneg-channel.svg17
-rw-r--r--silx/resources/gui/icons/compare-mode-vline.pngbin1079 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/compare-mode-vline.svg17
-rwxr-xr-xsilx/resources/gui/icons/crop.pngbin642 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/crop.svg6
-rw-r--r--silx/resources/gui/icons/crosshair.pngbin1196 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/crosshair.svg2
-rw-r--r--silx/resources/gui/icons/cube-back.pngbin737 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/cube-back.svg9
-rw-r--r--silx/resources/gui/icons/cube-bottom.pngbin833 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/cube-bottom.svg9
-rw-r--r--silx/resources/gui/icons/cube-front.pngbin708 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/cube-front.svg9
-rw-r--r--silx/resources/gui/icons/cube-left.pngbin712 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/cube-left.svg9
-rw-r--r--silx/resources/gui/icons/cube-right.pngbin701 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/cube-right.svg9
-rw-r--r--silx/resources/gui/icons/cube-rotate.pngbin955 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/cube-rotate.svg12
-rw-r--r--silx/resources/gui/icons/cube-top.pngbin767 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/cube-top.svg9
-rw-r--r--silx/resources/gui/icons/cube.pngbin953 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/cube.svg10
-rw-r--r--silx/resources/gui/icons/description-description.pngbin756 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/description-description.svg13
-rw-r--r--silx/resources/gui/icons/description-error.pngbin952 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/description-error.svg13
-rw-r--r--silx/resources/gui/icons/description-name.pngbin822 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/description-name.svg13
-rw-r--r--silx/resources/gui/icons/description-program.pngbin767 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/description-program.svg13
-rw-r--r--silx/resources/gui/icons/description-title.pngbin707 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/description-title.svg13
-rw-r--r--silx/resources/gui/icons/description-value.pngbin833 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/description-value.svg13
-rwxr-xr-xsilx/resources/gui/icons/document-open.pngbin2676 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/document-open.svg2
-rwxr-xr-xsilx/resources/gui/icons/document-print.pngbin702 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/document-print.svg2
-rwxr-xr-xsilx/resources/gui/icons/document-save.pngbin535 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/document-save.svg2
-rwxr-xr-xsilx/resources/gui/icons/draw-brush.pngbin1466 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/draw-brush.svg30
-rwxr-xr-xsilx/resources/gui/icons/draw-pencil.pngbin1055 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/draw-pencil.svg2
-rwxr-xr-xsilx/resources/gui/icons/draw-rubber.pngbin1154 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/draw-rubber.svg2
-rw-r--r--silx/resources/gui/icons/edit-copy.pngbin2191 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/edit-copy.svg2
-rw-r--r--silx/resources/gui/icons/eye.pngbin755 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/eye.svg23
-rw-r--r--silx/resources/gui/icons/first.pngbin1177 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/first.svg15
-rwxr-xr-xsilx/resources/gui/icons/folder.pngbin2583 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/folder.svg2
-rw-r--r--silx/resources/gui/icons/image-mask.pngbin852 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/image-mask.svg11
-rwxr-xr-xsilx/resources/gui/icons/image-select-add.pngbin2531 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/image-select-add.svg10
-rwxr-xr-xsilx/resources/gui/icons/image-select-box.pngbin3036 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/image-select-box.svg2
-rwxr-xr-xsilx/resources/gui/icons/image-select-brush.pngbin3300 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/image-select-brush.svg2
-rwxr-xr-xsilx/resources/gui/icons/image-select-erase-rubber.pngbin1638 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/image-select-erase-rubber.svg18
-rwxr-xr-xsilx/resources/gui/icons/image-select-erase.pngbin2286 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/image-select-erase.svg21
-rwxr-xr-xsilx/resources/gui/icons/image.pngbin2572 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/image.svg7
-rw-r--r--silx/resources/gui/icons/item-0dim.pngbin305 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/item-0dim.svg4
-rw-r--r--silx/resources/gui/icons/item-1dim.pngbin674 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/item-1dim.svg4
-rw-r--r--silx/resources/gui/icons/item-2dim.pngbin233 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/item-2dim.svg4
-rw-r--r--silx/resources/gui/icons/item-3dim.pngbin582 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/item-3dim.svg8
-rw-r--r--silx/resources/gui/icons/item-ndim.pngbin947 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/item-ndim.svg26
-rw-r--r--silx/resources/gui/icons/item-none.pngbin637 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/item-none.svg5
-rw-r--r--silx/resources/gui/icons/item-object.pngbin836 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/item-object.svg14
-rw-r--r--silx/resources/gui/icons/last.pngbin1111 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/last.svg15
-rw-r--r--silx/resources/gui/icons/layer-nx.pngbin459 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/layer-nx.svg3
-rw-r--r--silx/resources/gui/icons/mask-clear-all.pngbin1383 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/mask-clear-all.svg2
-rw-r--r--silx/resources/gui/icons/mask-clear.pngbin1086 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/mask-clear.svg2
-rw-r--r--silx/resources/gui/icons/mask-invert.pngbin717 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/mask-invert.svg2
-rw-r--r--silx/resources/gui/icons/math-amplitude.pngbin526 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-amplitude.svg3
-rwxr-xr-xsilx/resources/gui/icons/math-average.pngbin571 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-average.svg9
-rwxr-xr-xsilx/resources/gui/icons/math-derive.pngbin593 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-derive.svg10
-rwxr-xr-xsilx/resources/gui/icons/math-energy.pngbin645 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-energy.svg22
-rwxr-xr-xsilx/resources/gui/icons/math-fit.pngbin768 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-fit.svg2
-rw-r--r--silx/resources/gui/icons/math-imaginary.pngbin630 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-imaginary.svg3
-rw-r--r--silx/resources/gui/icons/math-mean.pngbin1487 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-mean.svg2
-rwxr-xr-xsilx/resources/gui/icons/math-normalize.pngbin653 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-normalize.svg2
-rwxr-xr-xsilx/resources/gui/icons/math-peak-reset.pngbin1420 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-peak-reset.svg2
-rwxr-xr-xsilx/resources/gui/icons/math-peak-search.pngbin2163 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-peak-search.svg2
-rwxr-xr-xsilx/resources/gui/icons/math-peak.pngbin829 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-peak.svg2
-rw-r--r--silx/resources/gui/icons/math-phase-color-log.pngbin2256 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-phase-color-log.svg3
-rw-r--r--silx/resources/gui/icons/math-phase-color.pngbin2127 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-phase-color.svg3
-rw-r--r--silx/resources/gui/icons/math-phase.pngbin1868 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-phase.svg3
-rw-r--r--silx/resources/gui/icons/math-real.pngbin749 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-real.svg3
-rwxr-xr-xsilx/resources/gui/icons/math-sigma.pngbin744 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-sigma.svg2
-rwxr-xr-xsilx/resources/gui/icons/math-smooth.pngbin1243 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-smooth.svg2
-rw-r--r--silx/resources/gui/icons/math-square-amplitude.pngbin592 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-square-amplitude.svg3
-rwxr-xr-xsilx/resources/gui/icons/math-substract.pngbin845 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-substract.svg2
-rwxr-xr-xsilx/resources/gui/icons/math-swap-sign.pngbin1007 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-swap-sign.svg2
-rwxr-xr-xsilx/resources/gui/icons/math-ymin-to-zero.pngbin666 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/math-ymin-to-zero.svg2
-rw-r--r--silx/resources/gui/icons/median-filter.pngbin694 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/median-filter.svg8
-rw-r--r--silx/resources/gui/icons/next.pngbin1092 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/next.svg14
-rwxr-xr-xsilx/resources/gui/icons/normal.pngbin1264 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/normal.svg2
-rw-r--r--silx/resources/gui/icons/nxdata-axis-add.pngbin686 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/nxdata-axis-add.svg2
-rw-r--r--silx/resources/gui/icons/nxdata-axis-remove.pngbin967 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/nxdata-axis-remove.svg2
-rw-r--r--silx/resources/gui/icons/nxdata-create.pngbin867 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/nxdata-create.svg2
-rw-r--r--silx/resources/gui/icons/nxdata-remove.pngbin1265 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/nxdata-remove.svg2
-rw-r--r--silx/resources/gui/icons/pan.pngbin526 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/pan.svg9
-rw-r--r--silx/resources/gui/icons/pixel-intensities.pngbin654 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/pixel-intensities.svg8
-rwxr-xr-xsilx/resources/gui/icons/plot-grid.pngbin446 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-grid.svg13
-rw-r--r--silx/resources/gui/icons/plot-roi-above.pngbin999 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-roi-above.svg4
-rw-r--r--silx/resources/gui/icons/plot-roi-below.pngbin988 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-roi-below.svg4
-rw-r--r--silx/resources/gui/icons/plot-roi-between.pngbin1021 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-roi-between.svg4
-rwxr-xr-xsilx/resources/gui/icons/plot-roi-reset.pngbin2063 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-roi-reset.svg2
-rwxr-xr-xsilx/resources/gui/icons/plot-roi.pngbin903 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-roi.svg2
-rw-r--r--silx/resources/gui/icons/plot-symbols.pngbin672 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-symbols.svg2
-rwxr-xr-xsilx/resources/gui/icons/plot-toggle-points.pngbin484 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-toggle-points.svg2
-rwxr-xr-xsilx/resources/gui/icons/plot-widget.pngbin1093 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-widget.svg18
-rwxr-xr-xsilx/resources/gui/icons/plot-window-image.pngbin1188 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-window-image.svg4
-rwxr-xr-xsilx/resources/gui/icons/plot-window.pngbin955 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-window.svg11
-rwxr-xr-xsilx/resources/gui/icons/plot-xauto.pngbin626 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-xauto.svg2
-rwxr-xr-xsilx/resources/gui/icons/plot-xlog.pngbin679 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-xlog.svg2
-rwxr-xr-xsilx/resources/gui/icons/plot-yauto.pngbin676 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-yauto.svg2
-rwxr-xr-xsilx/resources/gui/icons/plot-ydown.pngbin701 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-ydown.svg2
-rwxr-xr-xsilx/resources/gui/icons/plot-ylog.pngbin772 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-ylog.svg2
-rwxr-xr-xsilx/resources/gui/icons/plot-yup.pngbin667 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/plot-yup.svg2
-rw-r--r--silx/resources/gui/icons/pointing-hand.pngbin680 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/pointing-hand.svg2
-rw-r--r--silx/resources/gui/icons/previous.pngbin1151 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/previous.svg14
-rw-r--r--silx/resources/gui/icons/process-working.mngbin15966 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/00.pngbin778 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/01.pngbin789 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/02.pngbin785 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/03.pngbin785 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/04.pngbin766 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/05.pngbin777 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/06.pngbin784 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/07.pngbin783 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/08.pngbin762 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/09.pngbin781 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/10.pngbin771 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/11.pngbin768 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/12.pngbin759 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/13.pngbin767 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/14.pngbin778 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/15.pngbin760 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/16.pngbin754 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/17.pngbin782 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/18.pngbin775 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/19.pngbin764 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/20.pngbin764 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/21.pngbin772 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/22.pngbin769 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/23.pngbin773 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/24.pngbin757 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/25.pngbin759 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/26.pngbin774 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/27.pngbin766 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/28.pngbin760 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/29.pngbin777 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/process-working/30.pngbin775 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/profile-clear.pngbin917 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/profile-clear.svg2
-rw-r--r--silx/resources/gui/icons/profile1D.pngbin347 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/profile1D.svg9
-rw-r--r--silx/resources/gui/icons/profile2D.pngbin1403 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/profile2D.svg12
-rwxr-xr-xsilx/resources/gui/icons/remove.pngbin680 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/remove.svg2
-rw-r--r--silx/resources/gui/icons/rm.pngbin348 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/rm.svg2
-rw-r--r--silx/resources/gui/icons/rotate-3d.pngbin760 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/rotate-3d.svg7
-rwxr-xr-xsilx/resources/gui/icons/rudder.pngbin877 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/rudder.svg2
-rwxr-xr-xsilx/resources/gui/icons/selected.pngbin1411 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/selected.svg2
-rwxr-xr-xsilx/resources/gui/icons/shape-circle-solid.pngbin562 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/shape-circle-solid.svg5
-rwxr-xr-xsilx/resources/gui/icons/shape-circle.pngbin722 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/shape-circle.svg5
-rw-r--r--silx/resources/gui/icons/shape-cross.pngbin356 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/shape-cross.svg2
-rw-r--r--silx/resources/gui/icons/shape-diagonal-directed.pngbin542 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/shape-diagonal-directed.svg4
-rwxr-xr-xsilx/resources/gui/icons/shape-diagonal.pngbin461 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/shape-diagonal.svg5
-rwxr-xr-xsilx/resources/gui/icons/shape-ellipse-solid.pngbin541 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/shape-ellipse-solid.svg5
-rw-r--r--silx/resources/gui/icons/shape-ellipse.pngbin643 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/shape-ellipse.svg2
-rwxr-xr-xsilx/resources/gui/icons/shape-horizontal.pngbin301 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/shape-horizontal.svg5
-rwxr-xr-xsilx/resources/gui/icons/shape-polygon.pngbin819 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/shape-polygon.svg2
-rwxr-xr-xsilx/resources/gui/icons/shape-rectangle.pngbin337 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/shape-rectangle.svg2
-rwxr-xr-xsilx/resources/gui/icons/shape-square.pngbin417 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/shape-square.svg2
-rwxr-xr-xsilx/resources/gui/icons/shape-vertical.pngbin294 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/shape-vertical.svg2
-rwxr-xr-xsilx/resources/gui/icons/silx.pngbin2048 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/silx.svg37
-rw-r--r--silx/resources/gui/icons/slice-cross.pngbin1057 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/slice-cross.svg2
-rw-r--r--silx/resources/gui/icons/slice-horizontal.pngbin967 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/slice-horizontal.svg4
-rw-r--r--silx/resources/gui/icons/slice-vertical.pngbin1023 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/slice-vertical.svg2
-rwxr-xr-xsilx/resources/gui/icons/sliders-off.pngbin1111 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/sliders-off.svg2
-rwxr-xr-xsilx/resources/gui/icons/sliders-on.pngbin691 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/sliders-on.svg2
-rwxr-xr-xsilx/resources/gui/icons/spec.pngbin1044 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/spec.svg2
-rw-r--r--silx/resources/gui/icons/stats-active-items.pngbin1521 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/stats-active-items.svg2
-rw-r--r--silx/resources/gui/icons/stats-visible-data.pngbin662 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/stats-visible-data.svg2
-rw-r--r--silx/resources/gui/icons/stats-whole-data.pngbin923 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/stats-whole-data.svg2
-rw-r--r--silx/resources/gui/icons/stats-whole-items.pngbin1333 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/stats-whole-items.svg2
-rw-r--r--silx/resources/gui/icons/tree-collapse-all.pngbin508 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/tree-collapse-all.svg2
-rw-r--r--silx/resources/gui/icons/tree-expand-all.pngbin602 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/tree-expand-all.svg2
-rw-r--r--silx/resources/gui/icons/tree-sort.pngbin655 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/tree-sort.svg5
-rw-r--r--silx/resources/gui/icons/view-1d.pngbin881 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/view-1d.svg8
-rw-r--r--silx/resources/gui/icons/view-2d-stack.pngbin710 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/view-2d-stack.svg6
-rw-r--r--silx/resources/gui/icons/view-2d.pngbin304 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/view-2d.svg4
-rw-r--r--silx/resources/gui/icons/view-3d.pngbin1073 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/view-3d.svg8
-rwxr-xr-xsilx/resources/gui/icons/view-fullscreen.pngbin1829 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/view-fullscreen.svg2
-rw-r--r--silx/resources/gui/icons/view-hdf5.pngbin1347 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/view-hdf5.svg5
-rw-r--r--silx/resources/gui/icons/view-nexus.pngbin1332 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/view-nexus.svg5
-rwxr-xr-xsilx/resources/gui/icons/view-nofullscreen.pngbin1799 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/view-nofullscreen.svg2
-rw-r--r--silx/resources/gui/icons/view-raw.pngbin641 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/view-raw.svg12
-rwxr-xr-xsilx/resources/gui/icons/view-refresh.pngbin1184 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/view-refresh.svg2
-rw-r--r--silx/resources/gui/icons/view-text.pngbin872 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/view-text.svg10
-rwxr-xr-xsilx/resources/gui/icons/window-new.pngbin698 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/window-new.svg2
-rw-r--r--silx/resources/gui/icons/zoom-back.pngbin1432 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/zoom-back.svg2
-rwxr-xr-xsilx/resources/gui/icons/zoom-in.pngbin1612 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/zoom-in.svg2
-rwxr-xr-xsilx/resources/gui/icons/zoom-original.pngbin1518 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/zoom-original.svg2
-rwxr-xr-xsilx/resources/gui/icons/zoom-out.pngbin1567 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/zoom-out.svg2
-rwxr-xr-xsilx/resources/gui/icons/zoom.pngbin1448 -> 0 bytes
-rw-r--r--silx/resources/gui/icons/zoom.svg2
-rw-r--r--silx/resources/gui/logo/silx.pngbin21257 -> 0 bytes
-rw-r--r--silx/resources/gui/logo/silx.svg118
-rw-r--r--silx/resources/opencl/addition.cl42
-rw-r--r--silx/resources/opencl/array_utils.cl73
-rw-r--r--silx/resources/opencl/backproj.cl232
-rw-r--r--silx/resources/opencl/backproj_helper.cl68
-rw-r--r--silx/resources/opencl/bitonic.cl569
-rw-r--r--silx/resources/opencl/codec/byte_offset.cl235
-rw-r--r--silx/resources/opencl/convolution.cl312
-rw-r--r--silx/resources/opencl/convolution_textures.cl374
-rw-r--r--silx/resources/opencl/doubleword.cl115
-rw-r--r--silx/resources/opencl/image/cast.cl181
-rw-r--r--silx/resources/opencl/image/histogram.cl178
-rw-r--r--silx/resources/opencl/image/map.cl85
-rw-r--r--silx/resources/opencl/image/max_min.cl207
-rw-r--r--silx/resources/opencl/kahan.cl143
-rw-r--r--silx/resources/opencl/linalg.cl88
-rw-r--r--silx/resources/opencl/medfilt.cl141
-rw-r--r--silx/resources/opencl/preprocess.cl567
-rw-r--r--silx/resources/opencl/proj.cl345
-rw-r--r--silx/resources/opencl/sparse.cl94
-rw-r--r--silx/resources/opencl/statistics.cl283
-rw-r--r--silx/setup.py54
-rw-r--r--silx/sx/__init__.py146
-rw-r--r--silx/sx/_plot.py623
-rw-r--r--silx/sx/_plot3d.py250
-rw-r--r--silx/test/__init__.py104
-rw-r--r--silx/test/test_resources.py200
-rw-r--r--silx/test/test_sx.py292
-rw-r--r--silx/test/test_version.py49
-rw-r--r--silx/test/utils.py204
-rw-r--r--silx/third_party/EdfFile.py1225
-rw-r--r--silx/third_party/TiffIO.py1268
-rw-r--r--silx/third_party/__init__.py33
-rw-r--r--silx/third_party/scipy_spatial.py51
-rw-r--r--silx/third_party/setup.py49
-rw-r--r--silx/utils/ExternalResources.py320
-rw-r--r--silx/utils/__init__.py28
-rw-r--r--silx/utils/_have_openmp.pxd49
-rw-r--r--silx/utils/array_like.py596
-rw-r--r--silx/utils/debug.py103
-rw-r--r--silx/utils/deprecation.py123
-rw-r--r--silx/utils/enum.py79
-rw-r--r--silx/utils/exceptions.py33
-rw-r--r--silx/utils/files.py56
-rw-r--r--silx/utils/html.py60
-rw-r--r--silx/utils/include/silx_store_openmp.h10
-rw-r--r--silx/utils/launcher.py295
-rwxr-xr-xsilx/utils/number.py143
-rw-r--r--silx/utils/property.py52
-rw-r--r--silx/utils/proxy.py241
-rw-r--r--silx/utils/retry.py264
-rw-r--r--silx/utils/setup.py43
-rwxr-xr-xsilx/utils/test/__init__.py59
-rw-r--r--silx/utils/test/test_array_like.py445
-rw-r--r--silx/utils/test/test_debug.py99
-rw-r--r--silx/utils/test/test_deprecation.py107
-rw-r--r--silx/utils/test/test_enum.py96
-rw-r--r--silx/utils/test/test_external_resources.py99
-rw-r--r--silx/utils/test/test_html.py61
-rw-r--r--silx/utils/test/test_launcher.py204
-rw-r--r--silx/utils/test/test_launcher_command.py47
-rw-r--r--silx/utils/test/test_number.py186
-rw-r--r--silx/utils/test/test_proxy.py344
-rw-r--r--silx/utils/test/test_retry.py179
-rwxr-xr-xsilx/utils/test/test_testutils.py105
-rw-r--r--silx/utils/test/test_weakref.py330
-rwxr-xr-xsilx/utils/testutils.py333
-rw-r--r--silx/utils/weakref.py361
1032 files changed, 0 insertions, 207848 deletions
diff --git a/silx/__init__.py b/silx/__init__.py
deleted file mode 100644
index 2892572..0000000
--- a/silx/__init__.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2015-2018 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""The silx package contains the following main sub-packages:
-
-- silx.gui: Qt widgets for data visualization and data file browsing
-- silx.image: Some processing functions for 2D images
-- silx.io: Reading and writing data files (HDF5/NeXus, SPEC, ...)
-- silx.math: Some processing functions for 1D, 2D, 3D, nD arrays
-- silx.opencl: OpenCL-based data processing
-- silx.sx: High-level silx functions suited for (I)Python console.
-- silx.utils: Miscellaneous convenient functions
-
-See silx documentation: http://www.silx.org/doc/silx/latest/
-"""
-
-from __future__ import absolute_import, print_function, division
-
-__authors__ = ["Jérôme Kieffer"]
-__license__ = "MIT"
-__date__ = "26/04/2018"
-
-import os as _os
-import logging as _logging
-from ._config import Config as _Config
-
-config = _Config()
-"""Global configuration shared with the whole library"""
-
-# 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__)))
-
-try:
- from ._version import __date__ as date # noqa
- from ._version import version, version_info, hexversion, strictversion # noqa
-except ImportError:
- raise RuntimeError("Do NOT use %s from its sources: build it and use the built version" % project)
diff --git a/silx/__main__.py b/silx/__main__.py
deleted file mode 100644
index f832a09..0000000
--- a/silx/__main__.py
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/usr/bin/env python
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2017-2021 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 describe silx applications which are available through
-the silx launcher.
-
-Your environment should provide a command `silx`. You can reach help with
-`silx --help`, and check the version with `silx --version`.
-"""
-
-__authors__ = ["V. Valls", "P. Knobel"]
-__license__ = "MIT"
-__date__ = "07/06/2018"
-
-
-import logging
-logging.basicConfig()
-
-import multiprocessing
-import sys
-from silx.utils.launcher import Launcher
-import silx._version
-
-
-def main():
- """Main function of the launcher
-
- This function is referenced in the setup.py file, to create a
- launcher script generated by setuptools.
-
- :rtype: int
- :returns: The execution status
- """
- multiprocessing.freeze_support()
-
- launcher = Launcher(prog="silx", version=silx._version.version)
- launcher.add_command("view",
- module_name="silx.app.view.main",
- 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
-
-
-if __name__ == "__main__":
- # executed when using python -m PROJECT_NAME
- status = main()
- sys.exit(status)
diff --git a/silx/_config.py b/silx/_config.py
deleted file mode 100644
index fb0e409..0000000
--- a/silx/_config.py
+++ /dev/null
@@ -1,148 +0,0 @@
-#!/usr/bin/env python
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2017-2019 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""This module contains library wide configuration.
-"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "09/11/2018"
-
-
-class Config(object):
- """
- Class containing shared global configuration for the silx library.
-
- .. versionadded:: 0.8
- """
-
- DEFAULT_PLOT_BACKEND = "matplotlib", "opengl"
- """Default plot backend.
-
- It will be used as default backend for all the next created PlotWidget.
-
- This attribute can be set with:
-
- - 'matplotlib' (default) or 'mpl'
- - 'opengl', 'gl'
- - 'none'
- - A :class:`silx.gui.plot.backend.BackendBase.BackendBase` class
- - A callable returning backend class or binding name
-
- If multiple backends are provided, the first available one is used.
-
- .. versionadded:: 0.8
- """
-
- DEFAULT_COLORMAP_NAME = 'gray'
- """Default LUT for the plot widgets.
-
- The available list of names are available in the module
- :module:`silx.gui.colors`.
-
- .. versionadded:: 0.8
- """
-
- DEFAULT_PLOT_IMAGE_Y_AXIS_ORIENTATION = 'upward'
- """Default Y-axis orientation for plot widget displaying images.
-
- This attribute can be set with:
-
- - 'upward' (default), which set the origin to the bottom with an upward
- orientation.
- - 'downward', which set the origin to the top with a backward orientation.
-
- It will have an influence on:
-
- - :class:`silx.gui.plot.StackWidget`
- - :class:`silx.gui.plot.ComplexImageView`
- - :class:`silx.gui.plot.Plot2D`
- - :class:`silx.gui.plot.ImageView`
-
- .. versionadded:: 0.8
- """
-
- DEFAULT_PLOT_CURVE_COLORS = ['#000000', # black
- '#0000ff', # blue
- '#ff0000', # red
- '#00ff00', # green
- '#ff66ff', # pink
- '#ffff00', # yellow
- '#a52a2a', # brown
- '#00ffff', # cyan
- '#ff00ff', # magenta
- '#ff9900', # orange
- '#6600ff', # violet
- '#a0a0a4', # grey
- '#000080', # darkBlue
- '#800000', # darkRed
- '#008000', # darkGreen
- '#008080', # darkCyan
- '#800080', # darkMagenta
- '#808000', # darkYellow
- '#660000'] # darkBrown
- """Default list of colors for plot widget displaying curves.
-
- It will have an influence on:
-
- - :class:`silx.gui.plot.PlotWidget`
-
- .. versionadded:: 0.9
- """
-
- DEFAULT_PLOT_CURVE_SYMBOL_MODE = False
- """Whether to display curves with markers or not by default in PlotWidget.
-
- It will have an influence on PlotWidget curve items.
-
- .. versionadded:: 0.10
- """
-
- DEFAULT_PLOT_SYMBOL = 'o'
- """Default marker of the item.
-
- It will have an influence on PlotWidget items
-
- Supported symbols:
-
- - 'o', 'Circle'
- - 'd', 'Diamond'
- - 's', 'Square'
- - '+', 'Plus'
- - 'x', 'Cross'
- - '.', 'Point'
- - ',', 'Pixel'
- - '', 'None'
-
- .. versionadded:: 0.10
- """
-
- DEFAULT_PLOT_SYMBOL_SIZE = 6.0
- """Default marker size of the item.
-
- It will have an influence on PlotWidget items
-
- .. versionadded:: 0.10
- """
diff --git a/silx/app/__init__.py b/silx/app/__init__.py
deleted file mode 100644
index 3af680c..0000000
--- a/silx/app/__init__.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016-2018 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""This package contains the application provided by the launcher"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "30/03/2017"
diff --git a/silx/app/convert.py b/silx/app/convert.py
deleted file mode 100644
index 7e601ce..0000000
--- a/silx/app/convert.py
+++ /dev/null
@@ -1,525 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-# Copyright (C) 2017-2018 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ############################################################################*/
-"""Convert silx supported data files into HDF5 files"""
-
-__authors__ = ["P. Knobel"]
-__license__ = "MIT"
-__date__ = "05/02/2019"
-
-import ast
-import os
-import argparse
-from glob import glob
-import logging
-import re
-import time
-import numpy
-import six
-
-import silx.io
-from silx.io.specfile import is_specfile
-from silx.io import fabioh5
-
-_logger = logging.getLogger(__name__)
-"""Module logger"""
-
-
-def c_format_string_to_re(pattern_string):
- """
-
- :param pattern_string: C style format string with integer patterns
- (e.g. "%d", "%04d").
- Not supported: fixed length padded with whitespaces (e.g "%4d", "%-4d")
- :return: Equivalent regular expression (e.g. "\\d+", "\\d{4}")
- """
- # escape dots and backslashes
- pattern_string = pattern_string.replace("\\", "\\\\")
- pattern_string = pattern_string.replace(".", r"\.")
-
- # %d
- pattern_string = pattern_string.replace("%d", r"([-+]?\d+)")
-
- # %0nd
- for sub_pattern in re.findall(r"%0\d+d", pattern_string):
- n = int(re.search(r"%0(\d+)d", sub_pattern).group(1))
- if n == 1:
- re_sub_pattern = r"([+-]?\d)"
- else:
- re_sub_pattern = r"([\d+-]\d{%d})" % (n - 1)
- pattern_string = pattern_string.replace(sub_pattern, re_sub_pattern, 1)
-
- return pattern_string
-
-
-def drop_indices_before_begin(filenames, regex, begin):
- """
-
- :param List[str] filenames: list of filenames
- :param str regex: Regexp used to find indices in a filename
- :param str begin: Comma separated list of begin indices
- :return: List of filenames with only indices >= begin
- """
- begin_indices = list(map(int, begin.split(",")))
- output_filenames = []
- for fname in filenames:
- m = re.match(regex, fname)
- file_indices = list(map(int, m.groups()))
- if len(file_indices) != len(begin_indices):
- raise IOError("Number of indices found in filename "
- "does not match number of parsed end indices.")
- good_indices = True
- for i, fidx in enumerate(file_indices):
- if fidx < begin_indices[i]:
- good_indices = False
- if good_indices:
- output_filenames.append(fname)
- return output_filenames
-
-
-def drop_indices_after_end(filenames, regex, end):
- """
-
- :param List[str] filenames: list of filenames
- :param str regex: Regexp used to find indices in a filename
- :param str end: Comma separated list of end indices
- :return: List of filenames with only indices <= end
- """
- end_indices = list(map(int, end.split(",")))
- output_filenames = []
- for fname in filenames:
- m = re.match(regex, fname)
- file_indices = list(map(int, m.groups()))
- if len(file_indices) != len(end_indices):
- raise IOError("Number of indices found in filename "
- "does not match number of parsed end indices.")
- good_indices = True
- for i, fidx in enumerate(file_indices):
- if fidx > end_indices[i]:
- good_indices = False
- if good_indices:
- output_filenames.append(fname)
- return output_filenames
-
-
-def are_files_missing_in_series(filenames, regex):
- """Return True if any file is missing in a list of filenames
- that are supposed to follow a pattern.
-
- :param List[str] filenames: list of filenames
- :param str regex: Regexp used to find indices in a filename
- :return: boolean
- :raises AssertionError: if a filename does not match the regexp
- """
- previous_indices = None
- for fname in filenames:
- m = re.match(regex, fname)
- assert m is not None, \
- "regex %s does not match filename %s" % (fname, regex)
- new_indices = list(map(int, m.groups()))
- if previous_indices is not None:
- for old_idx, new_idx in zip(previous_indices, new_indices):
- if (new_idx - old_idx) > 1:
- _logger.error("Index increment > 1 in file series: "
- "previous idx %d, next idx %d",
- old_idx, new_idx)
- return True
- previous_indices = new_indices
- return False
-
-
-def are_all_specfile(filenames):
- """Return True if all files in a list are SPEC files.
- :param List[str] filenames: list of filenames
- """
- for fname in filenames:
- if not is_specfile(fname):
- return False
- return True
-
-
-def contains_specfile(filenames):
- """Return True if any file in a list are SPEC files.
- :param List[str] filenames: list of filenames
- """
- for fname in filenames:
- if is_specfile(fname):
- return True
- return False
-
-
-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, TIFF, SPEC...). When specifying multiple '
- 'files, you cannot specify both fabio images and SPEC files. '
- 'Multiple SPEC files will simply be concatenated, with one '
- 'entry per scan. Multiple image files will be merged into '
- 'a single entry with a stack of images.')
- # input_files and --filepattern are mutually exclusive
- parser.add_argument(
- '--file-pattern',
- help='File name pattern for loading a series of indexed image files '
- '(toto_%%04d.edf). This argument is incompatible with argument '
- 'input_files. If an output URI with a HDF5 path is provided, '
- 'only the content of the NXdetector group will be copied there. '
- 'If no HDF5 path, or just "/", is given, a complete NXdata '
- 'structure will be created.')
- parser.add_argument(
- '-o', '--output-uri',
- default=time.strftime("%Y%m%d-%H%M%S") + '.h5',
- help='Output file name (HDF5). An URI can be provided to write'
- ' the data into a specific group in the output file: '
- '/path/to/file::/path/to/group. '
- 'If not provided, the filename defaults to a timestamp:'
- ' YYYYmmdd-HHMMSS.h5')
- 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(
- '--begin',
- help='First file index, or first file indices to be considered. '
- 'This argument only makes sense when used together with '
- '--file-pattern. Provide as many start indices as there '
- 'are indices in the file pattern, separated by commas. '
- 'Examples: "--filepattern toto_%%d.edf --begin 100", '
- ' "--filepattern toto_%%d_%%04d_%%02d.edf --begin 100,2000,5".')
- parser.add_argument(
- '--end',
- help='Last file index, or last file indices to be considered. '
- 'The same rules as with argument --begin apply. '
- 'Example: "--filepattern toto_%%d_%%d.edf --end 199,1999"')
- parser.add_argument(
- '--add-root-group',
- action="store_true",
- help='This option causes each input file to be written to a '
- 'specific root group with the same name as the file. When '
- 'merging multiple input files, this can help preventing conflicts'
- ' when datasets have the same name (see --overwrite-data). '
- 'This option is ignored when using --file-pattern.')
- 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. This 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:])
-
- if options.debug:
- logging.root.setLevel(logging.DEBUG)
-
- # Import after parsing --debug
- try:
- # it should be loaded before h5py
- import hdf5plugin # noqa
- except ImportError:
- _logger.debug("Backtrace", exc_info=True)
- hdf5plugin = None
-
- import h5py
-
- try:
- from silx.io.convert import write_to_h5
- except ImportError:
- _logger.debug("Backtrace", exc_info=True)
- write_to_h5 = None
-
- 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)
-
- # Process input arguments (mutually exclusive arguments)
- if bool(options.input_files) == bool(options.file_pattern is not None):
- if not options.input_files:
- message = "You must specify either input files (at least one), "
- message += "or a file pattern."
- else:
- message = "You cannot specify input files and a file pattern"
- message += " at the same time."
- _logger.error(message)
- return -1
- elif options.input_files:
- # 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:
- # glob does not sort files, but the bash shell does
- options.input_files += sorted(globbed_files)
- else:
- # File series
- dirname = os.path.dirname(options.file_pattern)
- file_pattern_re = c_format_string_to_re(options.file_pattern) + "$"
- files_in_dir = glob(os.path.join(dirname, "*"))
- _logger.debug("""
- Processing file_pattern
- dirname: %s
- file_pattern_re: %s
- files_in_dir: %s
- """, dirname, file_pattern_re, files_in_dir)
-
- options.input_files = sorted(list(filter(lambda name: re.match(file_pattern_re, name),
- files_in_dir)))
- _logger.debug("options.input_files: %s", options.input_files)
-
- if options.begin is not None:
- options.input_files = drop_indices_before_begin(options.input_files,
- file_pattern_re,
- options.begin)
- _logger.debug("options.input_files after applying --begin: %s",
- options.input_files)
-
- if options.end is not None:
- options.input_files = drop_indices_after_end(options.input_files,
- file_pattern_re,
- options.end)
- _logger.debug("options.input_files after applying --end: %s",
- options.input_files)
-
- if are_files_missing_in_series(options.input_files,
- file_pattern_re):
- _logger.error("File missing in the file series. Aborting.")
- return -1
-
- if not options.input_files:
- _logger.error("No file matching --file-pattern found.")
- return -1
-
- # Test that the output path is writeable
- 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-' (default)."
- " Aborting. To append data to an existing file, "
- "use 'a' or 'r+'.",
- 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:
- try:
- compression = int(options.compression)
- except ValueError:
- compression = options.compression
- create_dataset_args["compression"] = 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
-
- if (len(options.input_files) > 1 and
- not contains_specfile(options.input_files) and
- not options.add_root_group) or options.file_pattern is not None:
- # File series -> stack of images
- input_group = fabioh5.File(file_series=options.input_files)
- if hdf5_path != "/":
- # we want to append only data and headers to an existing file
- input_group = input_group["/scan_0/instrument/detector_0"]
- with h5py.File(output_name, mode=options.mode) as h5f:
- write_to_h5(input_group, h5f,
- h5path=hdf5_path,
- overwrite_data=options.overwrite_data,
- create_dataset_args=create_dataset_args,
- min_size=options.min_size)
-
- elif len(options.input_files) == 1 or \
- are_all_specfile(options.input_files) or\
- options.add_root_group:
- # single file, or spec files
- h5paths_and_groups = []
- for input_name in options.input_files:
- hdf5_path_for_file = hdf5_path
- if options.add_root_group:
- hdf5_path_for_file = hdf5_path.rstrip("/") + "/" + os.path.basename(input_name)
- try:
- h5paths_and_groups.append((hdf5_path_for_file,
- silx.io.open(input_name)))
- except IOError:
- _logger.error("Cannot read file %s. If this is a file format "
- "supported by the fabio library, you can try to"
- " install fabio (`pip install fabio`)."
- " Aborting conversion.",
- input_name)
- return -1
-
- with h5py.File(output_name, mode=options.mode) as h5f:
- for hdf5_path_for_file, input_group in h5paths_and_groups:
- write_to_h5(input_group, h5f,
- h5path=hdf5_path_for_file,
- overwrite_data=options.overwrite_data,
- create_dataset_args=create_dataset_args,
- min_size=options.min_size)
-
- else:
- # multiple file, SPEC and fabio images mixed
- _logger.error("Multiple files with incompatible formats specified. "
- "You can provide multiple SPEC files or multiple image "
- "files, but not both.")
- return -1
-
- with h5py.File(output_name, mode="r+") as h5f:
- # append "silx convert" to the creator attribute, for NeXus files
- previous_creator = h5f.attrs.get("creator", u"")
- creator = "silx convert (v%s)" % silx.version
- # only if it not already there
- if creator not in previous_creator:
- if not previous_creator:
- new_creator = creator
- else:
- new_creator = previous_creator + "; " + creator
- h5f.attrs["creator"] = numpy.array(
- new_creator,
- dtype=h5py.special_dtype(vlen=six.text_type))
-
- return 0
diff --git a/silx/app/setup.py b/silx/app/setup.py
deleted file mode 100644
index 85c3662..0000000
--- a/silx/app/setup.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# 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.
-#
-# ############################################################################*/
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "23/04/2018"
-
-from numpy.distutils.misc_util import Configuration
-
-
-def configuration(parent_package='', top_path=None):
- config = Configuration('app', parent_package, top_path)
- config.add_subpackage('test')
- config.add_subpackage('view')
- return config
-
-
-if __name__ == "__main__":
- from numpy.distutils.core import setup
- setup(configuration=configuration)
diff --git a/silx/app/test/__init__.py b/silx/app/test/__init__.py
deleted file mode 100644
index 7c91134..0000000
--- a/silx/app/test/__init__.py
+++ /dev/null
@@ -1,39 +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.
-#
-# ###########################################################################*/
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "06/06/2018"
-
-import unittest
-
-from ..view import test as 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
deleted file mode 100644
index 857f30c..0000000
--- a/silx/app/test/test_convert.py
+++ /dev/null
@@ -1,167 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016-2018 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""Module testing silx.app.convert"""
-
-__authors__ = ["P. Knobel"]
-__license__ = "MIT"
-__date__ = "17/01/2018"
-
-
-import os
-import sys
-import tempfile
-import unittest
-import io
-import gc
-import h5py
-
-import silx
-from .. import convert
-from silx.utils import testutils
-from silx.io.utils import h5py_read_dataset
-
-
-# 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)
-
- 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)
-
- @testutils.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)
-
- 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_info < (3, ):
- fd.write(sftext)
- else:
- fd.write(bytes(sftext, 'ascii'))
-
- # convert it
- h5name = os.path.join(tempdir, "output.h5")
- assert not os.path.isfile(h5name)
- command_list = ["convert", "-m", "w",
- 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 = h5py_read_dataset(h5f["/1.2/title"])
- if sys.version_info < (3, ):
- title12 = title12.encode("utf-8")
- self.assertEqual(title12,
- "aaaaaa")
-
- creator = h5f.attrs.get("creator")
- self.assertIsNotNone(creator, "No creator attribute in NXroot group")
- if sys.version_info < (3, ):
- creator = creator.encode("utf-8")
- self.assertIn("silx convert (v%s)" % silx.version, creator)
-
- # 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_.py b/silx/app/test_.py
deleted file mode 100644
index a8e58bf..0000000
--- a/silx/app/test_.py
+++ /dev/null
@@ -1,159 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-# Copyright (C) 2016-2018 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ############################################################################*/
-"""Launch unittests of the library"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "12/01/2018"
-
-import sys
-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
- """
- from silx.test import utils
-
- 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("--qt-binding", dest="qt_binding", default=None,
- help="Force using a Qt binding: 'PyQt5' or 'PySide2'")
- utils.test_options.add_parser_argument(parser)
-
- 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 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
- elif binding == "pyside2":
- _logger.info("Force using PySide2")
- import PySide2.QtCore # noqa
- else:
- raise ValueError("Qt binding '%s' is unknown" % options.qt_binding)
-
- # Configure test options
- utils.test_options.configure(options)
-
- # 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/About.py b/silx/app/view/About.py
deleted file mode 100644
index a2b430f..0000000
--- a/silx/app/view/About.py
+++ /dev/null
@@ -1,257 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-# Copyright (C) 2016-2019 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ############################################################################*/
-"""About box for Silx viewer"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "05/07/2018"
-
-import os
-import sys
-
-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">loaded</font>'
- else:
- template = '<b>%s</b> is <font color="red">not loaded</font>'
- return template % name
-
- @staticmethod
- def __formatOptionalFilters(name, isAvailable):
- """Utils to format availability of features"""
- if isAvailable:
- template = '<b>%s</b> is <font color="green">available</font>'
- else:
- template = '<b>%s</b> is <font color="red">not available</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>
- """
-
- optionals = []
- optionals.append(self.__formatOptionalLibraries("H5py", "h5py" in sys.modules))
- optionals.append(self.__formatOptionalLibraries("FabIO", "fabio" in sys.modules))
-
- try:
- import h5py.version
- if h5py.version.hdf5_version_tuple >= (1, 10, 2):
- # Previous versions only return True if the filter was first used
- # to decode a dataset
- import h5py.h5z
- FILTER_LZ4 = 32004
- FILTER_BITSHUFFLE = 32008
- filters = [
- ("HDF5 LZ4 filter", FILTER_LZ4),
- ("HDF5 Bitshuffle filter", FILTER_BITSHUFFLE),
- ]
- for name, filterId in filters:
- isAvailable = h5py.h5z.filter_avail(filterId)
- optionals.append(self.__formatOptionalFilters(name, isAvailable))
- else:
- optionals.append(self.__formatOptionalLibraries("hdf5plugin", "hdf5plugin" in sys.modules))
- except ImportError:
- pass
-
- # Access to the logo in SVG or PNG
- logo = icons.getQFile("silx:" + os.path.join("gui", "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(optionals),
- 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/view/ApplicationContext.py b/silx/app/view/ApplicationContext.py
deleted file mode 100644
index 8693848..0000000
--- a/silx/app/view/ApplicationContext.py
+++ /dev/null
@@ -1,194 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-# Copyright (C) 2016-2018 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ############################################################################*/
-"""Browse a data file with a GUI"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "23/05/2018"
-
-import weakref
-import logging
-
-import silx
-from silx.gui.data.DataViews import DataViewHooks
-from silx.gui.colors import Colormap
-from silx.gui.dialog.ColormapDialog import ColormapDialog
-
-
-_logger = logging.getLogger(__name__)
-
-
-class ApplicationContext(DataViewHooks):
- """
- Store the conmtext of the application
-
- It overwrites the DataViewHooks to custom the use of the DataViewer for
- the silx view application.
-
- - Create a single colormap shared with all the views
- - Create a single colormap dialog shared with all the views
- """
-
- def __init__(self, parent, settings=None):
- self.__parent = weakref.ref(parent)
- self.__defaultColormap = None
- self.__defaultColormapDialog = None
- self.__settings = settings
- self.__recentFiles = []
-
- def getSettings(self):
- """Returns actual application settings.
-
- :rtype: qt.QSettings
- """
- return self.__settings
-
- def restoreLibrarySettings(self):
- """Restore the library settings, which must be done early"""
- settings = self.__settings
- if settings is None:
- return
- settings.beginGroup("library")
- plotBackend = settings.value("plot.backend", "")
- plotImageYAxisOrientation = settings.value("plot-image.y-axis-orientation", "")
- settings.endGroup()
-
- if plotBackend != "":
- silx.config.DEFAULT_PLOT_BACKEND = plotBackend
- if plotImageYAxisOrientation != "":
- silx.config.DEFAULT_PLOT_IMAGE_Y_AXIS_ORIENTATION = plotImageYAxisOrientation
-
- def restoreSettings(self):
- """Restore the settings of all the application"""
- settings = self.__settings
- if settings is None:
- return
- parent = self.__parent()
- parent.restoreSettings(settings)
-
- settings.beginGroup("colormap")
- byteArray = settings.value("default", None)
- if byteArray is not None:
- try:
- colormap = Colormap()
- colormap.restoreState(byteArray)
- self.__defaultColormap = colormap
- except Exception:
- _logger.debug("Backtrace", exc_info=True)
- settings.endGroup()
-
- self.__recentFiles = []
- settings.beginGroup("recent-files")
- for index in range(1, 10 + 1):
- if not settings.contains("path%d" % index):
- break
- filePath = settings.value("path%d" % index)
- self.__recentFiles.append(filePath)
- settings.endGroup()
-
- def saveSettings(self):
- """Save the settings of all the application"""
- settings = self.__settings
- if settings is None:
- return
- parent = self.__parent()
- parent.saveSettings(settings)
-
- if self.__defaultColormap is not None:
- settings.beginGroup("colormap")
- settings.setValue("default", self.__defaultColormap.saveState())
- settings.endGroup()
-
- settings.beginGroup("library")
- settings.setValue("plot.backend", silx.config.DEFAULT_PLOT_BACKEND)
- settings.setValue("plot-image.y-axis-orientation", silx.config.DEFAULT_PLOT_IMAGE_Y_AXIS_ORIENTATION)
- settings.endGroup()
-
- settings.beginGroup("recent-files")
- for index in range(0, 11):
- key = "path%d" % (index + 1)
- if index < len(self.__recentFiles):
- filePath = self.__recentFiles[index]
- settings.setValue(key, filePath)
- else:
- settings.remove(key)
- settings.endGroup()
-
- def getRecentFiles(self):
- """Returns the list of recently opened files.
-
- The list is limited to the last 10 entries. The newest file path is
- in first.
-
- :rtype: List[str]
- """
- return self.__recentFiles
-
- def pushRecentFile(self, filePath):
- """Push a new recent file to the list.
-
- If the file is duplicated in the list, all duplications are removed
- before inserting the new filePath.
-
- If the list becan bigger than 10 items, oldest paths are removed.
-
- :param filePath: File path to push
- """
- # Remove old occurencies
- self.__recentFiles[:] = (f for f in self.__recentFiles if f != filePath)
- self.__recentFiles.insert(0, filePath)
- while len(self.__recentFiles) > 10:
- self.__recentFiles.pop()
-
- def clearRencentFiles(self):
- """Clear the history of the rencent files.
- """
- self.__recentFiles[:] = []
-
- def getColormap(self, view):
- """Returns a default colormap.
-
- Override from DataViewHooks
-
- :rtype: Colormap
- """
- if self.__defaultColormap is None:
- self.__defaultColormap = Colormap(name="viridis")
- return self.__defaultColormap
-
- def getColormapDialog(self, view):
- """Returns a shared color dialog as default for all the views.
-
- Override from DataViewHooks
-
- :rtype: ColorDialog
- """
- if self.__defaultColormapDialog is None:
- parent = self.__parent()
- if parent is None:
- return None
- dialog = ColormapDialog(parent=parent)
- dialog.setModal(False)
- self.__defaultColormapDialog = dialog
- return self.__defaultColormapDialog
diff --git a/silx/app/view/CustomNxdataWidget.py b/silx/app/view/CustomNxdataWidget.py
deleted file mode 100644
index 72c9940..0000000
--- a/silx/app/view/CustomNxdataWidget.py
+++ /dev/null
@@ -1,1008 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-# Copyright (C) 2016-2018 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ############################################################################*/
-
-"""Widget to custom NXdata groups"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "15/06/2018"
-
-import logging
-import numpy
-import weakref
-
-from silx.gui import qt
-from silx.io import commonh5
-import silx.io.nxdata
-from silx.gui.hdf5._utils import Hdf5DatasetMimeData
-from silx.gui.data.TextFormatter import TextFormatter
-from silx.gui.hdf5.Hdf5Formatter import Hdf5Formatter
-from silx.gui import icons
-
-
-_logger = logging.getLogger(__name__)
-_formatter = TextFormatter()
-_hdf5Formatter = Hdf5Formatter(textFormatter=_formatter)
-
-
-class _RowItems(qt.QStandardItem):
- """Define the list of items used for a specific row."""
-
- def type(self):
- return qt.QStandardItem.UserType + 1
-
- def getRowItems(self):
- """Returns the list of items used for a specific row.
-
- The first item should be this class.
-
- :rtype: List[qt.QStandardItem]
- """
- raise NotImplementedError()
-
-
-class _DatasetItemRow(_RowItems):
- """Define a row which can contain a dataset."""
-
- def __init__(self, label="", dataset=None):
- """Constructor"""
- super(_DatasetItemRow, self).__init__(label)
- self.setEditable(False)
- self.setDropEnabled(False)
- self.setDragEnabled(False)
-
- self.__name = qt.QStandardItem()
- self.__name.setEditable(False)
- self.__name.setDropEnabled(True)
-
- self.__type = qt.QStandardItem()
- self.__type.setEditable(False)
- self.__type.setDropEnabled(False)
- self.__type.setDragEnabled(False)
-
- self.__shape = qt.QStandardItem()
- self.__shape.setEditable(False)
- self.__shape.setDropEnabled(False)
- self.__shape.setDragEnabled(False)
-
- self.setDataset(dataset)
-
- def getDefaultFormatter(self):
- """Get the formatter used to display dataset informations.
-
- :rtype: Hdf5Formatter
- """
- return _hdf5Formatter
-
- def setDataset(self, dataset):
- """Set the dataset stored in this item.
-
- :param Union[numpy.ndarray,h5py.Dataset,silx.io.commonh5.Dataset] dataset:
- The dataset to store.
- """
- self.__dataset = dataset
- if self.__dataset is not None:
- name = self.__dataset.name
-
- if silx.io.is_dataset(dataset):
- type_ = self.getDefaultFormatter().humanReadableType(dataset)
- shape = self.getDefaultFormatter().humanReadableShape(dataset)
-
- if dataset.shape is None:
- icon_name = "item-none"
- elif len(dataset.shape) < 4:
- icon_name = "item-%ddim" % len(dataset.shape)
- else:
- icon_name = "item-ndim"
- icon = icons.getQIcon(icon_name)
- else:
- type_ = ""
- shape = ""
- icon = qt.QIcon()
- else:
- name = ""
- type_ = ""
- shape = ""
- icon = qt.QIcon()
-
- self.__icon = icon
- self.__name.setText(name)
- self.__name.setDragEnabled(self.__dataset is not None)
- self.__name.setIcon(self.__icon)
- self.__type.setText(type_)
- self.__shape.setText(shape)
-
- parent = self.parent()
- if parent is not None:
- self.parent()._datasetUpdated()
-
- def getDataset(self):
- """Returns the dataset stored within the item."""
- return self.__dataset
-
- def getRowItems(self):
- """Returns the list of items used for a specific row.
-
- The first item should be this class.
-
- :rtype: List[qt.QStandardItem]
- """
- return [self, self.__name, self.__type, self.__shape]
-
-
-class _DatasetAxisItemRow(_DatasetItemRow):
- """Define a row describing an axis."""
-
- def __init__(self):
- """Constructor"""
- super(_DatasetAxisItemRow, self).__init__()
-
- def setAxisId(self, axisId):
- """Set the id of the axis (the first axis is 0)
-
- :param int axisId: Identifier of this axis.
- """
- self.__axisId = axisId
- label = "Axis %d" % (axisId + 1)
- self.setText(label)
-
- def getAxisId(self):
- """Returns the identifier of this axis.
-
- :rtype: int
- """
- return self.__axisId
-
-
-class _NxDataItem(qt.QStandardItem):
- """
- Define a custom NXdata.
- """
-
- def __init__(self):
- """Constructor"""
- qt.QStandardItem.__init__(self)
- self.__error = None
- self.__title = None
- self.__axes = []
- self.__virtual = None
-
- item = _DatasetItemRow("Signal", None)
- self.appendRow(item.getRowItems())
- self.__signal = item
-
- self.setEditable(False)
- self.setDragEnabled(False)
- self.setDropEnabled(False)
- self.__setError(None)
-
- def getRowItems(self):
- """Returns the list of items used for a specific row.
-
- The first item should be this class.
-
- :rtype: List[qt.QStandardItem]
- """
- row = [self]
- for _ in range(3):
- item = qt.QStandardItem("")
- item.setEditable(False)
- item.setDragEnabled(False)
- item.setDropEnabled(False)
- row.append(item)
- return row
-
- def _datasetUpdated(self):
- """Called when the NXdata contained of the item have changed.
-
- It invalidates the NXdata stored and send an event `sigNxdataUpdated`.
- """
- self.__virtual = None
- self.__setError(None)
- model = self.model()
- if model is not None:
- model.sigNxdataUpdated.emit(self.index())
-
- def createVirtualGroup(self):
- """Returns a new virtual Group using a NeXus NXdata structure to store
- data
-
- :rtype: silx.io.commonh5.Group
- """
- name = ""
- if self.__title is not None:
- name = self.__title
- virtual = commonh5.Group(name)
- virtual.attrs["NX_class"] = "NXdata"
-
- if self.__title is not None:
- virtual.attrs["title"] = self.__title
-
- if self.__signal is not None:
- signal = self.__signal.getDataset()
- if signal is not None:
- # Could be done using a link instead of a copy
- node = commonh5.DatasetProxy("signal", target=signal)
- virtual.attrs["signal"] = "signal"
- virtual.add_node(node)
-
- axesAttr = []
- for i, axis in enumerate(self.__axes):
- if axis is None:
- name = "."
- else:
- axis = axis.getDataset()
- if axis is None:
- name = "."
- else:
- name = "axis%d" % i
- node = commonh5.DatasetProxy(name, target=axis)
- virtual.add_node(node)
- axesAttr.append(name)
-
- if axesAttr != []:
- virtual.attrs["axes"] = numpy.array(axesAttr)
-
- validator = silx.io.nxdata.NXdata(virtual)
- if not validator.is_valid:
- message = "<html>"
- message += "This NXdata is not consistant"
- message += "<ul>"
- for issue in validator.issues:
- message += "<li>%s</li>" % issue
- message += "</ul>"
- message += "</html>"
- self.__setError(message)
- else:
- self.__setError(None)
- return virtual
-
- def isValid(self):
- """Returns true if the stored NXdata is valid
-
- :rtype: bool
- """
- return self.__error is None
-
- def getVirtualGroup(self):
- """Returns a cached virtual Group using a NeXus NXdata structure to
- store data.
-
- If the stored NXdata was invalidated, :meth:`createVirtualGroup` is
- internally called to update the cache.
-
- :rtype: silx.io.commonh5.Group
- """
- if self.__virtual is None:
- self.__virtual = self.createVirtualGroup()
- return self.__virtual
-
- def getTitle(self):
- """Returns the title of the NXdata
-
- :rtype: str
- """
- return self.text()
-
- def setTitle(self, title):
- """Set the title of the NXdata
-
- :param str title: The title of this NXdata
- """
- self.setText(title)
-
- def __setError(self, error):
- """Set the error message in case of the current state of the stored
- NXdata is not valid.
-
- :param str error: Message to display
- """
- self.__error = error
- style = qt.QApplication.style()
- if error is None:
- message = ""
- icon = style.standardIcon(qt.QStyle.SP_DirLinkIcon)
- else:
- message = error
- icon = style.standardIcon(qt.QStyle.SP_MessageBoxCritical)
- self.setIcon(icon)
- self.setToolTip(message)
-
- def getError(self):
- """Returns the error message in case the NXdata is not valid.
-
- :rtype: str"""
- return self.__error
-
- def setSignalDataset(self, dataset):
- """Set the dataset to use as signal with this NXdata.
-
- :param Union[numpy.ndarray,h5py.Dataset,silx.io.commonh5.Dataset] dataset:
- The dataset to use as signal.
- """
-
- self.__signal.setDataset(dataset)
- self._datasetUpdated()
-
- def getSignalDataset(self):
- """Returns the dataset used as signal.
-
- :rtype: Union[numpy.ndarray,h5py.Dataset,silx.io.commonh5.Dataset]
- """
- return self.__signal.getDataset()
-
- def setAxesDatasets(self, datasets):
- """Set all the available dataset used as axes.
-
- Axes will be created or removed from the GUI in order to provide the
- same amount of requested axes.
-
- A `None` element is an axes with no dataset.
-
- :param List[Union[numpy.ndarray,h5py.Dataset,silx.io.commonh5.Dataset,None]] datasets:
- List of dataset to use as axes.
- """
- for i, dataset in enumerate(datasets):
- if i < len(self.__axes):
- mustAppend = False
- item = self.__axes[i]
- else:
- mustAppend = True
- item = _DatasetAxisItemRow()
- item.setAxisId(i)
- item.setDataset(dataset)
- if mustAppend:
- self.__axes.append(item)
- self.appendRow(item.getRowItems())
-
- # Clean up extra axis
- for i in range(len(datasets), len(self.__axes)):
- item = self.__axes.pop(len(datasets))
- self.removeRow(item.row())
-
- self._datasetUpdated()
-
- def getAxesDatasets(self):
- """Returns available axes as dataset.
-
- A `None` element is an axes with no dataset.
-
- :rtype: List[Union[numpy.ndarray,h5py.Dataset,silx.io.commonh5.Dataset,None]]
- """
- datasets = []
- for axis in self.__axes:
- datasets.append(axis.getDataset())
- return datasets
-
-
-class _Model(qt.QStandardItemModel):
- """Model storing a list of custom NXdata items.
-
- Supports drag and drop of datasets.
- """
-
- sigNxdataUpdated = qt.Signal(qt.QModelIndex)
- """Emitted when stored NXdata was edited"""
-
- def __init__(self, parent=None):
- """Constructor"""
- qt.QStandardItemModel.__init__(self, parent)
- root = self.invisibleRootItem()
- root.setDropEnabled(True)
- root.setDragEnabled(False)
-
- def supportedDropActions(self):
- """Inherited method to redefine supported drop actions."""
- return qt.Qt.CopyAction | qt.Qt.MoveAction
-
- def mimeTypes(self):
- """Inherited method to redefine draggable mime types."""
- return [Hdf5DatasetMimeData.MIME_TYPE]
-
- def mimeData(self, indexes):
- """
- Returns an object that contains serialized items of data corresponding
- to the list of indexes specified.
-
- :param List[qt.QModelIndex] indexes: List of indexes
- :rtype: qt.QMimeData
- """
- if len(indexes) > 1:
- return None
- if len(indexes) == 0:
- return None
-
- qindex = indexes[0]
- qindex = self.index(qindex.row(), 0, parent=qindex.parent())
- item = self.itemFromIndex(qindex)
- if isinstance(item, _DatasetItemRow):
- dataset = item.getDataset()
- if dataset is None:
- return None
- else:
- mimeData = Hdf5DatasetMimeData(dataset=item.getDataset())
- else:
- mimeData = None
- return mimeData
-
- def dropMimeData(self, mimedata, action, row, column, parentIndex):
- """Inherited method to handle a drop operation to this model."""
- if action == qt.Qt.IgnoreAction:
- return True
-
- if mimedata.hasFormat(Hdf5DatasetMimeData.MIME_TYPE):
- if row != -1 or column != -1:
- # It is not a drop on a specific item
- return False
- item = self.itemFromIndex(parentIndex)
- if item is None or item is self.invisibleRootItem():
- # Drop at the end
- dataset = mimedata.dataset()
- if silx.io.is_dataset(dataset):
- self.createFromSignal(dataset)
- elif silx.io.is_group(dataset):
- nxdata = dataset
- try:
- self.createFromNxdata(nxdata)
- except ValueError:
- _logger.error("Error while dropping a group as an NXdata")
- _logger.debug("Backtrace", exc_info=True)
- return False
- else:
- _logger.error("Dropping a wrong object")
- return False
- else:
- item = item.parent().child(item.row(), 0)
- if not isinstance(item, _DatasetItemRow):
- # Dropped at a bad place
- return False
- dataset = mimedata.dataset()
- if silx.io.is_dataset(dataset):
- item.setDataset(dataset)
- else:
- _logger.error("Dropping a wrong object")
- return False
- return True
-
- return False
-
- def __getNxdataByTitle(self, title):
- """Returns an NXdata item by its title, else None.
-
- :rtype: Union[_NxDataItem,None]
- """
- for row in range(self.rowCount()):
- qindex = self.index(row, 0)
- item = self.itemFromIndex(qindex)
- if item.getTitle() == title:
- return item
- return None
-
- def findFreeNxdataTitle(self):
- """Returns an NXdata title which is not yet used.
-
- :rtype: str
- """
- for i in range(self.rowCount() + 1):
- name = "NXData #%d" % (i + 1)
- group = self.__getNxdataByTitle(name)
- if group is None:
- break
- return name
-
- def createNewNxdata(self, name=None):
- """Create a new NXdata item.
-
- :param Union[str,None] name: A title for the new NXdata
- """
- item = _NxDataItem()
- if name is None:
- name = self.findFreeNxdataTitle()
- item.setTitle(name)
- self.appendRow(item.getRowItems())
-
- def createFromSignal(self, dataset):
- """Create a new NXdata item from a signal dataset.
-
- This signal will also define an amount of axes according to its number
- of dimensions.
-
- :param Union[numpy.ndarray,h5py.Dataset,silx.io.commonh5.Dataset] dataset:
- A dataset uses as signal.
- """
-
- item = _NxDataItem()
- name = self.findFreeNxdataTitle()
- item.setTitle(name)
- item.setSignalDataset(dataset)
- item.setAxesDatasets([None] * len(dataset.shape))
- self.appendRow(item.getRowItems())
-
- def createFromNxdata(self, nxdata):
- """Create a new custom NXdata item from an existing NXdata group.
-
- If the NXdata is not valid, nothing is created, and an exception is
- returned.
-
- :param Union[h5py.Group,silx.io.commonh5.Group] nxdata: An h5py group
- following the NXData specification.
- :raise ValueError:If `nxdata` is not valid.
- """
- validator = silx.io.nxdata.NXdata(nxdata)
- if validator.is_valid:
- item = _NxDataItem()
- title = validator.title
- if title in [None or ""]:
- title = self.findFreeNxdataTitle()
- item.setTitle(title)
- item.setSignalDataset(validator.signal)
- item.setAxesDatasets(validator.axes)
- self.appendRow(item.getRowItems())
- else:
- raise ValueError("Not a valid NXdata")
-
- def removeNxdataItem(self, item):
- """Remove an NXdata item from this model.
-
- :param _NxDataItem item: An item
- """
- if isinstance(item, _NxDataItem):
- parent = item.parent()
- assert(parent is None)
- model = item.model()
- model.removeRow(item.row())
- else:
- _logger.error("Unexpected item")
-
- def appendAxisToNxdataItem(self, item):
- """Append a new axes to this item (or the NXdata item own by this item).
-
- :param Union[_NxDataItem,qt.QStandardItem] item: An item
- """
- if item is not None and not isinstance(item, _NxDataItem):
- item = item.parent()
- nxdataItem = item
- if isinstance(item, _NxDataItem):
- datasets = nxdataItem.getAxesDatasets()
- datasets.append(None)
- nxdataItem.setAxesDatasets(datasets)
- else:
- _logger.error("Unexpected item")
-
- def removeAxisItem(self, item):
- """Remove an axis item from this model.
-
- :param _DatasetAxisItemRow item: An axis item
- """
- if isinstance(item, _DatasetAxisItemRow):
- axisId = item.getAxisId()
- nxdataItem = item.parent()
- datasets = nxdataItem.getAxesDatasets()
- del datasets[axisId]
- nxdataItem.setAxesDatasets(datasets)
- else:
- _logger.error("Unexpected item")
-
-
-class CustomNxDataToolBar(qt.QToolBar):
- """A specialised toolbar to manage custom NXdata model and items."""
-
- def __init__(self, parent=None):
- """Constructor"""
- super(CustomNxDataToolBar, self).__init__(parent=parent)
- self.__nxdataWidget = None
- self.__initContent()
- # Initialize action state
- self.__currentSelectionChanged(qt.QModelIndex(), qt.QModelIndex())
-
- def __initContent(self):
- """Create all expected actions and set the content of this toolbar."""
- action = qt.QAction("Create a new custom NXdata", self)
- action.setIcon(icons.getQIcon("nxdata-create"))
- action.triggered.connect(self.__createNewNxdata)
- self.addAction(action)
- self.__addNxDataAction = action
-
- action = qt.QAction("Remove the selected NXdata", self)
- action.setIcon(icons.getQIcon("nxdata-remove"))
- action.triggered.connect(self.__removeSelectedNxdata)
- self.addAction(action)
- self.__removeNxDataAction = action
-
- self.addSeparator()
-
- action = qt.QAction("Create a new axis to the selected NXdata", self)
- action.setIcon(icons.getQIcon("nxdata-axis-add"))
- action.triggered.connect(self.__appendNewAxisToSelectedNxdata)
- self.addAction(action)
- self.__addNxDataAxisAction = action
-
- action = qt.QAction("Remove the selected NXdata axis", self)
- action.setIcon(icons.getQIcon("nxdata-axis-remove"))
- action.triggered.connect(self.__removeSelectedAxis)
- self.addAction(action)
- self.__removeNxDataAxisAction = action
-
- def __getSelectedItem(self):
- """Get the selected item from the linked CustomNxdataWidget.
-
- :rtype: qt.QStandardItem
- """
- selectionModel = self.__nxdataWidget.selectionModel()
- index = selectionModel.currentIndex()
- if not index.isValid():
- return
- model = self.__nxdataWidget.model()
- index = model.index(index.row(), 0, index.parent())
- item = model.itemFromIndex(index)
- return item
-
- def __createNewNxdata(self):
- """Create a new NXdata item to the linked CustomNxdataWidget."""
- if self.__nxdataWidget is None:
- return
- model = self.__nxdataWidget.model()
- model.createNewNxdata()
-
- def __removeSelectedNxdata(self):
- """Remove the NXdata item currently selected in the linked
- CustomNxdataWidget."""
- if self.__nxdataWidget is None:
- return
- model = self.__nxdataWidget.model()
- item = self.__getSelectedItem()
- model.removeNxdataItem(item)
-
- def __appendNewAxisToSelectedNxdata(self):
- """Append a new axis to the NXdata item currently selected in the
- linked CustomNxdataWidget."""
- if self.__nxdataWidget is None:
- return
- model = self.__nxdataWidget.model()
- item = self.__getSelectedItem()
- model.appendAxisToNxdataItem(item)
-
- def __removeSelectedAxis(self):
- """Remove the axis item currently selected in the linked
- CustomNxdataWidget."""
- if self.__nxdataWidget is None:
- return
- model = self.__nxdataWidget.model()
- item = self.__getSelectedItem()
- model.removeAxisItem(item)
-
- def setCustomNxDataWidget(self, widget):
- """Set the linked CustomNxdataWidget to this toolbar."""
- assert(isinstance(widget, CustomNxdataWidget))
- if self.__nxdataWidget is not None:
- selectionModel = self.__nxdataWidget.selectionModel()
- selectionModel.currentChanged.disconnect(self.__currentSelectionChanged)
- self.__nxdataWidget = widget
- if self.__nxdataWidget is not None:
- selectionModel = self.__nxdataWidget.selectionModel()
- selectionModel.currentChanged.connect(self.__currentSelectionChanged)
-
- def __currentSelectionChanged(self, current, previous):
- """Update the actions according to the linked CustomNxdataWidget
- item selection"""
- if not current.isValid():
- item = None
- else:
- model = self.__nxdataWidget.model()
- index = model.index(current.row(), 0, current.parent())
- item = model.itemFromIndex(index)
- self.__removeNxDataAction.setEnabled(isinstance(item, _NxDataItem))
- self.__removeNxDataAxisAction.setEnabled(isinstance(item, _DatasetAxisItemRow))
- self.__addNxDataAxisAction.setEnabled(isinstance(item, _NxDataItem) or isinstance(item, _DatasetItemRow))
-
-
-class _HashDropZones(qt.QStyledItemDelegate):
- """Delegate item displaying a drop zone when the item do not contains
- dataset."""
-
- def __init__(self, parent=None):
- """Constructor"""
- super(_HashDropZones, self).__init__(parent)
- pen = qt.QPen()
- pen.setColor(qt.QColor("#D0D0D0"))
- pen.setStyle(qt.Qt.DotLine)
- pen.setWidth(2)
- self.__dropPen = pen
-
- def paint(self, painter, option, index):
- """
- Paint the item
-
- :param qt.QPainter painter: A painter
- :param qt.QStyleOptionViewItem option: Options of the item to paint
- :param qt.QModelIndex index: Index of the item to paint
- """
- displayDropZone = False
- if index.isValid():
- model = index.model()
- rowIndex = model.index(index.row(), 0, index.parent())
- rowItem = model.itemFromIndex(rowIndex)
- if isinstance(rowItem, _DatasetItemRow):
- displayDropZone = rowItem.getDataset() is None
-
- if displayDropZone:
- painter.save()
-
- # Draw background if selected
- if option.state & qt.QStyle.State_Selected:
- colorGroup = qt.QPalette.Inactive
- if option.state & qt.QStyle.State_Active:
- colorGroup = qt.QPalette.Active
- if not option.state & qt.QStyle.State_Enabled:
- colorGroup = qt.QPalette.Disabled
- brush = option.palette.brush(colorGroup, qt.QPalette.Highlight)
- painter.fillRect(option.rect, brush)
-
- painter.setPen(self.__dropPen)
- painter.drawRect(option.rect.adjusted(3, 3, -3, -3))
- painter.restore()
- else:
- qt.QStyledItemDelegate.paint(self, painter, option, index)
-
-
-class CustomNxdataWidget(qt.QTreeView):
- """Widget providing a table displaying and allowing to custom virtual
- NXdata."""
-
- sigNxdataItemUpdated = qt.Signal(qt.QStandardItem)
- """Emitted when the NXdata from an NXdata item was edited"""
-
- sigNxdataItemRemoved = qt.Signal(qt.QStandardItem)
- """Emitted when an NXdata item was removed"""
-
- def __init__(self, parent=None):
- """Constructor"""
- qt.QTreeView.__init__(self, parent=None)
- self.__model = _Model(self)
- self.__model.setColumnCount(4)
- self.__model.setHorizontalHeaderLabels(["Name", "Dataset", "Type", "Shape"])
- self.setModel(self.__model)
-
- self.setItemDelegateForColumn(1, _HashDropZones(self))
-
- self.__model.sigNxdataUpdated.connect(self.__nxdataUpdate)
- self.__model.rowsAboutToBeRemoved.connect(self.__rowsAboutToBeRemoved)
- self.__model.rowsAboutToBeInserted.connect(self.__rowsAboutToBeInserted)
-
- header = self.header()
- if qt.qVersion() < "5.0":
- setResizeMode = header.setResizeMode
- else:
- setResizeMode = header.setSectionResizeMode
- setResizeMode(0, qt.QHeaderView.ResizeToContents)
- setResizeMode(1, qt.QHeaderView.Stretch)
- setResizeMode(2, qt.QHeaderView.ResizeToContents)
- setResizeMode(3, qt.QHeaderView.ResizeToContents)
-
- self.setSelectionMode(qt.QAbstractItemView.SingleSelection)
- self.setDropIndicatorShown(True)
- self.setDragDropOverwriteMode(True)
- self.setDragEnabled(True)
- self.viewport().setAcceptDrops(True)
-
- self.setContextMenuPolicy(qt.Qt.CustomContextMenu)
- self.customContextMenuRequested[qt.QPoint].connect(self.__executeContextMenu)
-
- def __rowsAboutToBeInserted(self, parentIndex, start, end):
- if qt.qVersion()[0:2] == "5.":
- # FIXME: workaround for https://github.com/silx-kit/silx/issues/1919
- # Uses of ResizeToContents looks to break nice update of cells with Qt5
- # This patch make the view blinking
- self.repaint()
-
- def __rowsAboutToBeRemoved(self, parentIndex, start, end):
- """Called when an item was removed from the model."""
- items = []
- model = self.model()
- for index in range(start, end):
- qindex = model.index(index, 0, parent=parentIndex)
- item = self.__model.itemFromIndex(qindex)
- if isinstance(item, _NxDataItem):
- items.append(item)
- for item in items:
- self.sigNxdataItemRemoved.emit(item)
-
- if qt.qVersion()[0:2] == "5.":
- # FIXME: workaround for https://github.com/silx-kit/silx/issues/1919
- # Uses of ResizeToContents looks to break nice update of cells with Qt5
- # This patch make the view blinking
- self.repaint()
-
- def __nxdataUpdate(self, index):
- """Called when a virtual NXdata was updated from the model."""
- model = self.model()
- item = model.itemFromIndex(index)
- self.sigNxdataItemUpdated.emit(item)
-
- def createDefaultContextMenu(self, index):
- """Create a default context menu at this position.
-
- :param qt.QModelIndex index: Index of the item
- """
- index = self.__model.index(index.row(), 0, parent=index.parent())
- item = self.__model.itemFromIndex(index)
-
- menu = qt.QMenu()
-
- weakself = weakref.proxy(self)
-
- if isinstance(item, _NxDataItem):
- action = qt.QAction("Add a new axis", menu)
- action.triggered.connect(lambda: weakself.model().appendAxisToNxdataItem(item))
- action.setIcon(icons.getQIcon("nxdata-axis-add"))
- action.setIconVisibleInMenu(True)
- menu.addAction(action)
- menu.addSeparator()
- action = qt.QAction("Remove this NXdata", menu)
- action.triggered.connect(lambda: weakself.model().removeNxdataItem(item))
- action.setIcon(icons.getQIcon("remove"))
- action.setIconVisibleInMenu(True)
- menu.addAction(action)
- else:
- if isinstance(item, _DatasetItemRow):
- if item.getDataset() is not None:
- action = qt.QAction("Remove this dataset", menu)
- action.triggered.connect(lambda: item.setDataset(None))
- menu.addAction(action)
-
- if isinstance(item, _DatasetAxisItemRow):
- menu.addSeparator()
- action = qt.QAction("Remove this axis", menu)
- action.triggered.connect(lambda: weakself.model().removeAxisItem(item))
- action.setIcon(icons.getQIcon("remove"))
- action.setIconVisibleInMenu(True)
- menu.addAction(action)
-
- return menu
-
- def __executeContextMenu(self, point):
- """Execute the context menu at this position."""
- index = self.indexAt(point)
- menu = self.createDefaultContextMenu(index)
- if menu is None or menu.isEmpty():
- return
- menu.exec_(qt.QCursor.pos())
-
- def removeDatasetsFrom(self, root):
- """
- Remove all datasets provided by this root
-
- :param root: The root file of datasets to remove
- """
- for row in range(self.__model.rowCount()):
- qindex = self.__model.index(row, 0)
- item = self.model().itemFromIndex(qindex)
-
- edited = False
- datasets = item.getAxesDatasets()
- for i, dataset in enumerate(datasets):
- if dataset is not None:
- # That's an approximation, IS can't be used as h5py generates
- # To objects for each requests to a node
- if dataset.file.filename == root.file.filename:
- datasets[i] = None
- edited = True
- if edited:
- item.setAxesDatasets(datasets)
-
- dataset = item.getSignalDataset()
- if dataset is not None:
- # That's an approximation, IS can't be used as h5py generates
- # To objects for each requests to a node
- if dataset.file.filename == root.file.filename:
- item.setSignalDataset(None)
-
- def replaceDatasetsFrom(self, removedRoot, loadedRoot):
- """
- Replace any dataset from any NXdata items using the same dataset name
- from another root.
-
- Usually used when a file was synchronized.
-
- :param removedRoot: The h5py root file which is replaced
- (which have to be removed)
- :param loadedRoot: The new h5py root file which have to be used
- instread.
- """
- for row in range(self.__model.rowCount()):
- qindex = self.__model.index(row, 0)
- item = self.model().itemFromIndex(qindex)
-
- edited = False
- datasets = item.getAxesDatasets()
- for i, dataset in enumerate(datasets):
- newDataset = self.__replaceDatasetRoot(dataset, removedRoot, loadedRoot)
- if dataset is not newDataset:
- datasets[i] = newDataset
- edited = True
- if edited:
- item.setAxesDatasets(datasets)
-
- dataset = item.getSignalDataset()
- newDataset = self.__replaceDatasetRoot(dataset, removedRoot, loadedRoot)
- if dataset is not newDataset:
- item.setSignalDataset(newDataset)
-
- def __replaceDatasetRoot(self, dataset, fromRoot, toRoot):
- """
- Replace the dataset by the same dataset name from another root.
- """
- if dataset is None:
- return None
-
- if dataset.file is None:
- # Not from the expected root
- return dataset
-
- # That's an approximation, IS can't be used as h5py generates
- # To objects for each requests to a node
- if dataset.file.filename == fromRoot.file.filename:
- # Try to find the same dataset name
- try:
- return toRoot[dataset.name]
- except Exception:
- _logger.debug("Backtrace", exc_info=True)
- return None
- else:
- # Not from the expected root
- return dataset
-
- def selectedItems(self):
- """Returns the list of selected items containing NXdata
-
- :rtype: List[qt.QStandardItem]
- """
- result = []
- for qindex in self.selectedIndexes():
- if qindex.column() != 0:
- continue
- if not qindex.isValid():
- continue
- item = self.__model.itemFromIndex(qindex)
- if not isinstance(item, _NxDataItem):
- continue
- result.append(item)
- return result
-
- def selectedNxdata(self):
- """Returns the list of selected NXdata
-
- :rtype: List[silx.io.commonh5.Group]
- """
- result = []
- for qindex in self.selectedIndexes():
- if qindex.column() != 0:
- continue
- if not qindex.isValid():
- continue
- item = self.__model.itemFromIndex(qindex)
- if not isinstance(item, _NxDataItem):
- continue
- result.append(item.getVirtualGroup())
- return result
diff --git a/silx/app/view/DataPanel.py b/silx/app/view/DataPanel.py
deleted file mode 100644
index 5d87381..0000000
--- a/silx/app/view/DataPanel.py
+++ /dev/null
@@ -1,192 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-# Copyright (C) 2018 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ############################################################################*/
-"""Browse a data file with a GUI"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "12/10/2018"
-
-import logging
-import os.path
-
-from silx.gui import qt
-from silx.gui.data.DataViewerFrame import DataViewerFrame
-
-
-_logger = logging.getLogger(__name__)
-
-
-class _HeaderLabel(qt.QLabel):
-
- def __init__(self, parent=None):
- qt.QLabel.__init__(self, parent=parent)
- self.setFrameShape(qt.QFrame.StyledPanel)
-
- def sizeHint(self):
- return qt.QSize(10, 30)
-
- def minimumSizeHint(self):
- return qt.QSize(10, 30)
-
- def setData(self, filename, path):
- if filename == "" and path == "":
- text = ""
- elif filename == "":
- text = path
- else:
- text = "%s::%s" % (filename, path)
- self.setText(text)
- tooltip = ""
- template = "<li><b>%s</b>: %s</li>"
- tooltip += template % ("Directory", os.path.dirname(filename))
- tooltip += template % ("File name", os.path.basename(filename))
- tooltip += template % ("Data path", path)
- tooltip = "<ul>%s</ul>" % tooltip
- tooltip = "<html>%s</html>" % tooltip
- self.setToolTip(tooltip)
-
- def paintEvent(self, event):
- painter = qt.QPainter(self)
-
- opt = qt.QStyleOptionHeader()
- opt.orientation = qt.Qt.Horizontal
- opt.text = self.text()
- opt.textAlignment = self.alignment()
- opt.direction = self.layoutDirection()
- opt.fontMetrics = self.fontMetrics()
- opt.palette = self.palette()
- opt.state = qt.QStyle.State_Active
- opt.position = qt.QStyleOptionHeader.Beginning
- style = self.style()
-
- # Background
- margin = -1
- opt.rect = self.rect().adjusted(margin, margin, -margin, -margin)
- style.drawControl(qt.QStyle.CE_HeaderSection, opt, painter, None)
-
- # Frame border and text
- super(_HeaderLabel, self).paintEvent(event)
-
-
-class DataPanel(qt.QWidget):
-
- def __init__(self, parent=None, context=None):
- qt.QWidget.__init__(self, parent=parent)
-
- self.__customNxdataItem = None
-
- self.__dataTitle = _HeaderLabel(self)
- self.__dataTitle.setVisible(False)
-
- self.__dataViewer = DataViewerFrame(self)
- self.__dataViewer.setGlobalHooks(context)
-
- layout = qt.QVBoxLayout(self)
- layout.setSpacing(0)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.addWidget(self.__dataTitle)
- layout.addWidget(self.__dataViewer)
-
- def getData(self):
- return self.__dataViewer.data()
-
- def getCustomNxdataItem(self):
- return self.__customNxdataItem
-
- def setData(self, data):
- self.__customNxdataItem = None
- self.__dataViewer.setData(data)
- self.__dataTitle.setVisible(data is not None)
- if data is not None:
- self.__dataTitle.setVisible(True)
- if hasattr(data, "name"):
- if hasattr(data, "file"):
- filename = str(data.file.filename)
- else:
- filename = ""
- path = data.name
- else:
- filename = ""
- path = ""
- self.__dataTitle.setData(filename, path)
-
- def setCustomDataItem(self, item):
- self.__customNxdataItem = item
- if item is not None:
- data = item.getVirtualGroup()
- else:
- data = None
- self.__dataViewer.setData(data)
- self.__dataTitle.setVisible(item is not None)
- if item is not None:
- text = item.text()
- self.__dataTitle.setText(text)
-
- def removeDatasetsFrom(self, root):
- """
- Remove all datasets provided by this root
-
- .. note:: This function do not update data stored inside
- customNxdataItem cause in the silx-view context this item is
- already updated on his own.
-
- :param root: The root file of datasets to remove
- """
- data = self.__dataViewer.data()
- if data is not None:
- if data.file is not None:
- # That's an approximation, IS can't be used as h5py generates
- # To objects for each requests to a node
- if data.file.filename == root.file.filename:
- self.__dataViewer.setData(None)
-
- def replaceDatasetsFrom(self, removedH5, loadedH5):
- """
- Replace any dataset from any NXdata items using the same dataset name
- from another root.
-
- Usually used when a file was synchronized.
-
- .. note:: This function do not update data stored inside
- customNxdataItem cause in the silx-view context this item is
- already updated on his own.
-
- :param removedRoot: The h5py root file which is replaced
- (which have to be removed)
- :param loadedRoot: The new h5py root file which have to be used
- instread.
- """
-
- data = self.__dataViewer.data()
- if data is not None:
- if data.file is not None:
- if data.file.filename == removedH5.file.filename:
- # Try to synchonize the viewed data
- try:
- # TODO: It have to update the data without changing the
- # view which is not so easy
- newData = loadedH5[data.name]
- self.__dataViewer.setData(newData)
- except Exception:
- _logger.debug("Backtrace", exc_info=True)
diff --git a/silx/app/view/Viewer.py b/silx/app/view/Viewer.py
deleted file mode 100644
index dd4d075..0000000
--- a/silx/app/view/Viewer.py
+++ /dev/null
@@ -1,971 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-# Copyright (C) 2016-2020 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.
-#
-# ############################################################################*/
-"""Browse a data file with a GUI"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "15/01/2019"
-
-
-import os
-import collections
-import logging
-import functools
-
-import silx.io.nxdata
-from silx.gui import qt
-from silx.gui import icons
-import silx.gui.hdf5
-from .ApplicationContext import ApplicationContext
-from .CustomNxdataWidget import CustomNxdataWidget
-from .CustomNxdataWidget import CustomNxDataToolBar
-from . import utils
-from silx.gui.utils import projecturl
-from .DataPanel import DataPanel
-
-
-_logger = logging.getLogger(__name__)
-
-
-class Viewer(qt.QMainWindow):
- """
- This window allows to browse a data file like images or HDF5 and it's
- content.
- """
-
- def __init__(self, parent=None, settings=None):
- """
- Constructor
- """
-
- qt.QMainWindow.__init__(self, parent)
- self.setWindowTitle("Silx viewer")
-
- silxIcon = icons.getQIcon("silx")
- self.setWindowIcon(silxIcon)
-
- self.__context = self.createApplicationContext(settings)
- self.__context.restoreLibrarySettings()
-
- self.__dialogState = None
- self.__customNxDataItem = None
- self.__treeview = silx.gui.hdf5.Hdf5TreeView(self)
- self.__treeview.setExpandsOnDoubleClick(False)
- """Silx HDF5 TreeView"""
-
- rightPanel = qt.QSplitter(self)
- rightPanel.setOrientation(qt.Qt.Vertical)
- self.__splitter2 = rightPanel
-
- self.__displayIt = None
- self.__treeWindow = self.__createTreeWindow(self.__treeview)
-
- # Custom the model to be able to manage the life cycle of the files
- treeModel = silx.gui.hdf5.Hdf5TreeModel(self.__treeview, ownFiles=False)
- treeModel.sigH5pyObjectLoaded.connect(self.__h5FileLoaded)
- treeModel.sigH5pyObjectRemoved.connect(self.__h5FileRemoved)
- treeModel.sigH5pyObjectSynchronized.connect(self.__h5FileSynchonized)
- treeModel.setDatasetDragEnabled(True)
- self.__treeModelSorted = silx.gui.hdf5.NexusSortFilterProxyModel(self.__treeview)
- self.__treeModelSorted.setSourceModel(treeModel)
- self.__treeModelSorted.sort(0, qt.Qt.AscendingOrder)
- self.__treeModelSorted.setSortCaseSensitivity(qt.Qt.CaseInsensitive)
-
- self.__treeview.setModel(self.__treeModelSorted)
- rightPanel.addWidget(self.__treeWindow)
-
- self.__customNxdata = CustomNxdataWidget(self)
- self.__customNxdata.setSelectionBehavior(qt.QAbstractItemView.SelectRows)
- # optimise the rendering
- self.__customNxdata.setUniformRowHeights(True)
- self.__customNxdata.setIconSize(qt.QSize(16, 16))
- self.__customNxdata.setExpandsOnDoubleClick(False)
-
- self.__customNxdataWindow = self.__createCustomNxdataWindow(self.__customNxdata)
- self.__customNxdataWindow.setVisible(False)
- rightPanel.addWidget(self.__customNxdataWindow)
-
- rightPanel.setStretchFactor(1, 1)
- rightPanel.setCollapsible(0, False)
- rightPanel.setCollapsible(1, False)
-
- self.__dataPanel = DataPanel(self, self.__context)
-
- spliter = qt.QSplitter(self)
- spliter.addWidget(rightPanel)
- spliter.addWidget(self.__dataPanel)
- spliter.setStretchFactor(1, 1)
- spliter.setCollapsible(0, False)
- spliter.setCollapsible(1, False)
- self.__splitter = spliter
-
- main_panel = qt.QWidget(self)
- layout = qt.QVBoxLayout()
- layout.addWidget(spliter)
- layout.setStretchFactor(spliter, 1)
- main_panel.setLayout(layout)
-
- self.setCentralWidget(main_panel)
-
- self.__treeview.activated.connect(self.displaySelectedData)
- self.__customNxdata.activated.connect(self.displaySelectedCustomData)
- self.__customNxdata.sigNxdataItemRemoved.connect(self.__customNxdataRemoved)
- self.__customNxdata.sigNxdataItemUpdated.connect(self.__customNxdataUpdated)
- self.__treeview.addContextMenuCallback(self.customContextMenu)
-
- treeModel = self.__treeview.findHdf5TreeModel()
- columns = list(treeModel.COLUMN_IDS)
- columns.remove(treeModel.VALUE_COLUMN)
- columns.remove(treeModel.NODE_COLUMN)
- columns.remove(treeModel.DESCRIPTION_COLUMN)
- columns.insert(1, treeModel.DESCRIPTION_COLUMN)
- self.__treeview.header().setSections(columns)
-
- self._iconUpward = icons.getQIcon('plot-yup')
- self._iconDownward = icons.getQIcon('plot-ydown')
-
- self.createActions()
- self.createMenus()
- self.__context.restoreSettings()
-
- def createApplicationContext(self, settings):
- return ApplicationContext(self, settings)
-
- def __createTreeWindow(self, treeView):
- toolbar = qt.QToolBar(self)
- toolbar.setIconSize(qt.QSize(16, 16))
- toolbar.setStyleSheet("QToolBar { border: 0px }")
-
- action = qt.QAction(toolbar)
- action.setIcon(icons.getQIcon("view-refresh"))
- action.setText("Refresh")
- action.setToolTip("Refresh all selected items")
- action.triggered.connect(self.__refreshSelected)
- action.setShortcut(qt.QKeySequence(qt.Qt.Key_F5))
- toolbar.addAction(action)
- treeView.addAction(action)
- self.__refreshAction = action
-
- # Another shortcut for refresh
- action = qt.QAction(toolbar)
- action.setShortcut(qt.QKeySequence(qt.Qt.ControlModifier + qt.Qt.Key_R))
- treeView.addAction(action)
- action.triggered.connect(self.__refreshSelected)
-
- action = qt.QAction(toolbar)
- # action.setIcon(icons.getQIcon("view-refresh"))
- action.setText("Close")
- action.setToolTip("Close selected item")
- action.triggered.connect(self.__removeSelected)
- action.setShortcut(qt.QKeySequence(qt.Qt.Key_Delete))
- treeView.addAction(action)
- self.__closeAction = action
-
- toolbar.addSeparator()
-
- action = qt.QAction(toolbar)
- action.setIcon(icons.getQIcon("tree-expand-all"))
- action.setText("Expand all")
- action.setToolTip("Expand all selected items")
- action.triggered.connect(self.__expandAllSelected)
- action.setShortcut(qt.QKeySequence(qt.Qt.ControlModifier + qt.Qt.Key_Plus))
- toolbar.addAction(action)
- treeView.addAction(action)
- self.__expandAllAction = action
-
- action = qt.QAction(toolbar)
- action.setIcon(icons.getQIcon("tree-collapse-all"))
- action.setText("Collapse all")
- action.setToolTip("Collapse all selected items")
- action.triggered.connect(self.__collapseAllSelected)
- action.setShortcut(qt.QKeySequence(qt.Qt.ControlModifier + qt.Qt.Key_Minus))
- toolbar.addAction(action)
- treeView.addAction(action)
- self.__collapseAllAction = action
-
- action = qt.QAction("&Sort file content", toolbar)
- action.setIcon(icons.getQIcon("tree-sort"))
- action.setToolTip("Toggle sorting of file content")
- action.setCheckable(True)
- action.setChecked(True)
- action.triggered.connect(self.setContentSorted)
- toolbar.addAction(action)
- treeView.addAction(action)
- self._sortContentAction = action
-
- widget = qt.QWidget(self)
- layout = qt.QVBoxLayout(widget)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(0)
- layout.addWidget(toolbar)
- layout.addWidget(treeView)
- return widget
-
- def __removeSelected(self):
- """Close selected items"""
- qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor)
-
- selection = self.__treeview.selectionModel()
- indexes = selection.selectedIndexes()
- selectedItems = []
- model = self.__treeview.model()
- h5files = set([])
- while len(indexes) > 0:
- index = indexes.pop(0)
- if index.column() != 0:
- continue
- h5 = model.data(index, role=silx.gui.hdf5.Hdf5TreeModel.H5PY_OBJECT_ROLE)
- rootIndex = index
- # Reach the root of the tree
- while rootIndex.parent().isValid():
- rootIndex = rootIndex.parent()
- rootRow = rootIndex.row()
- relativePath = self.__getRelativePath(model, rootIndex, index)
- selectedItems.append((rootRow, relativePath))
- h5files.add(h5.file)
-
- if len(h5files) != 0:
- model = self.__treeview.findHdf5TreeModel()
- for h5 in h5files:
- row = model.h5pyObjectRow(h5)
- model.removeH5pyObject(h5)
-
- qt.QApplication.restoreOverrideCursor()
-
- def __refreshSelected(self):
- """Refresh all selected items
- """
- qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor)
-
- selection = self.__treeview.selectionModel()
- indexes = selection.selectedIndexes()
- selectedItems = []
- model = self.__treeview.model()
- h5files = set([])
- while len(indexes) > 0:
- index = indexes.pop(0)
- if index.column() != 0:
- continue
- h5 = model.data(index, role=silx.gui.hdf5.Hdf5TreeModel.H5PY_OBJECT_ROLE)
- rootIndex = index
- # Reach the root of the tree
- while rootIndex.parent().isValid():
- rootIndex = rootIndex.parent()
- rootRow = rootIndex.row()
- relativePath = self.__getRelativePath(model, rootIndex, index)
- selectedItems.append((rootRow, relativePath))
- h5files.add(h5.file)
-
- if len(h5files) == 0:
- qt.QApplication.restoreOverrideCursor()
- return
-
- model = self.__treeview.findHdf5TreeModel()
- for h5 in h5files:
- self.__synchronizeH5pyObject(h5)
-
- model = self.__treeview.model()
- itemSelection = qt.QItemSelection()
- for rootRow, relativePath in selectedItems:
- rootIndex = model.index(rootRow, 0, qt.QModelIndex())
- index = self.__indexFromPath(model, rootIndex, relativePath)
- if index is None:
- continue
- indexEnd = model.index(index.row(), model.columnCount() - 1, index.parent())
- itemSelection.select(index, indexEnd)
- selection.select(itemSelection, qt.QItemSelectionModel.ClearAndSelect)
-
- qt.QApplication.restoreOverrideCursor()
-
- def __synchronizeH5pyObject(self, h5):
- model = self.__treeview.findHdf5TreeModel()
- # This is buggy right now while h5py do not allow to close a file
- # while references are still used.
- # FIXME: The architecture have to be reworked to support this feature.
- # model.synchronizeH5pyObject(h5)
-
- filename = h5.filename
- row = model.h5pyObjectRow(h5)
- index = self.__treeview.model().index(row, 0, qt.QModelIndex())
- paths = self.__getPathFromExpandedNodes(self.__treeview, index)
- model.removeH5pyObject(h5)
- model.insertFile(filename, row)
- index = self.__treeview.model().index(row, 0, qt.QModelIndex())
- self.__expandNodesFromPaths(self.__treeview, index, paths)
-
- def __getRelativePath(self, model, rootIndex, index):
- """Returns a relative path from an index to his rootIndex.
-
- If the path is empty the index is also the rootIndex.
- """
- path = ""
- while index.isValid():
- if index == rootIndex:
- return path
- name = model.data(index)
- if path == "":
- path = name
- else:
- path = name + "/" + path
- index = index.parent()
-
- # index is not a children of rootIndex
- raise ValueError("index is not a children of the rootIndex")
-
- def __getPathFromExpandedNodes(self, view, rootIndex):
- """Return relative path from the root index of the extended nodes"""
- model = view.model()
- rootPath = None
- paths = []
- indexes = [rootIndex]
- while len(indexes):
- index = indexes.pop(0)
- if not view.isExpanded(index):
- continue
-
- node = model.data(index, role=silx.gui.hdf5.Hdf5TreeModel.H5PY_ITEM_ROLE)
- path = node._getCanonicalName()
- if rootPath is None:
- rootPath = path
- path = path[len(rootPath):]
- paths.append(path)
-
- for child in range(model.rowCount(index)):
- childIndex = model.index(child, 0, index)
- indexes.append(childIndex)
- return paths
-
- def __indexFromPath(self, model, rootIndex, path):
- elements = path.split("/")
- if elements[0] == "":
- elements.pop(0)
- index = rootIndex
- while len(elements) != 0:
- element = elements.pop(0)
- found = False
- for child in range(model.rowCount(index)):
- childIndex = model.index(child, 0, index)
- name = model.data(childIndex)
- if element == name:
- index = childIndex
- found = True
- break
- if not found:
- return None
- return index
-
- def __expandNodesFromPaths(self, view, rootIndex, paths):
- model = view.model()
- for path in paths:
- index = self.__indexFromPath(model, rootIndex, path)
- if index is not None:
- view.setExpanded(index, True)
-
- def __expandAllSelected(self):
- """Expand all selected items of the tree.
-
- The depth is fixed to avoid infinite loop with recurssive links.
- """
- qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor)
-
- selection = self.__treeview.selectionModel()
- indexes = selection.selectedIndexes()
- model = self.__treeview.model()
- while len(indexes) > 0:
- index = indexes.pop(0)
- if isinstance(index, tuple):
- index, depth = index
- else:
- depth = 0
- if index.column() != 0:
- continue
-
- if depth > 10:
- # Avoid infinite loop with recursive links
- break
-
- if model.hasChildren(index):
- self.__treeview.setExpanded(index, True)
- for row in range(model.rowCount(index)):
- childIndex = model.index(row, 0, index)
- indexes.append((childIndex, depth + 1))
- qt.QApplication.restoreOverrideCursor()
-
- def __collapseAllSelected(self):
- """Collapse all selected items of the tree.
-
- The depth is fixed to avoid infinite loop with recurssive links.
- """
- selection = self.__treeview.selectionModel()
- indexes = selection.selectedIndexes()
- model = self.__treeview.model()
- while len(indexes) > 0:
- index = indexes.pop(0)
- if isinstance(index, tuple):
- index, depth = index
- else:
- depth = 0
- if index.column() != 0:
- continue
-
- if depth > 10:
- # Avoid infinite loop with recursive links
- break
-
- if model.hasChildren(index):
- self.__treeview.setExpanded(index, False)
- for row in range(model.rowCount(index)):
- childIndex = model.index(row, 0, index)
- indexes.append((childIndex, depth + 1))
-
- def __createCustomNxdataWindow(self, customNxdataWidget):
- toolbar = CustomNxDataToolBar(self)
- toolbar.setCustomNxDataWidget(customNxdataWidget)
- toolbar.setIconSize(qt.QSize(16, 16))
- toolbar.setStyleSheet("QToolBar { border: 0px }")
-
- widget = qt.QWidget(self)
- layout = qt.QVBoxLayout(widget)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(0)
- layout.addWidget(toolbar)
- layout.addWidget(customNxdataWidget)
- return widget
-
- def __h5FileLoaded(self, loadedH5):
- self.__context.pushRecentFile(loadedH5.file.filename)
- if loadedH5.file.filename == self.__displayIt:
- self.__displayIt = None
- self.displayData(loadedH5)
-
- def __h5FileRemoved(self, removedH5):
- self.__dataPanel.removeDatasetsFrom(removedH5)
- self.__customNxdata.removeDatasetsFrom(removedH5)
- removedH5.close()
-
- def __h5FileSynchonized(self, removedH5, loadedH5):
- self.__dataPanel.replaceDatasetsFrom(removedH5, loadedH5)
- self.__customNxdata.replaceDatasetsFrom(removedH5, loadedH5)
- removedH5.close()
-
- def closeEvent(self, event):
- self.__context.saveSettings()
-
- # Clean up as much as possible Python objects
- self.displayData(None)
- customModel = self.__customNxdata.model()
- customModel.clear()
- hdf5Model = self.__treeview.findHdf5TreeModel()
- hdf5Model.clear()
-
- def saveSettings(self, settings):
- """Save the window settings to this settings object
-
- :param qt.QSettings settings: Initialized settings
- """
- isFullScreen = bool(self.windowState() & qt.Qt.WindowFullScreen)
- if isFullScreen:
- # show in normal to catch the normal geometry
- self.showNormal()
-
- settings.beginGroup("mainwindow")
- settings.setValue("size", self.size())
- settings.setValue("pos", self.pos())
- settings.setValue("full-screen", isFullScreen)
- settings.endGroup()
-
- settings.beginGroup("mainlayout")
- settings.setValue("spliter", self.__splitter.sizes())
- settings.setValue("spliter2", self.__splitter2.sizes())
- isVisible = self.__customNxdataWindow.isVisible()
- settings.setValue("custom-nxdata-window-visible", isVisible)
- settings.endGroup()
-
- settings.beginGroup("content")
- isSorted = self._sortContentAction.isChecked()
- settings.setValue("is-sorted", isSorted)
- settings.endGroup()
-
- if isFullScreen:
- self.showFullScreen()
-
- def restoreSettings(self, settings):
- """Restore the window settings using this settings object
-
- :param qt.QSettings settings: Initialized settings
- """
- settings.beginGroup("mainwindow")
- size = settings.value("size", qt.QSize(640, 480))
- pos = settings.value("pos", qt.QPoint())
- isFullScreen = settings.value("full-screen", False)
- try:
- if not isinstance(isFullScreen, bool):
- isFullScreen = utils.stringToBool(isFullScreen)
- except ValueError:
- isFullScreen = False
- settings.endGroup()
-
- settings.beginGroup("mainlayout")
- try:
- data = settings.value("spliter")
- data = [int(d) for d in data]
- self.__splitter.setSizes(data)
- except Exception:
- _logger.debug("Backtrace", exc_info=True)
- try:
- data = settings.value("spliter2")
- data = [int(d) for d in data]
- self.__splitter2.setSizes(data)
- except Exception:
- _logger.debug("Backtrace", exc_info=True)
- isVisible = settings.value("custom-nxdata-window-visible", False)
- try:
- if not isinstance(isVisible, bool):
- isVisible = utils.stringToBool(isVisible)
- except ValueError:
- isVisible = False
- self.__customNxdataWindow.setVisible(isVisible)
- self._displayCustomNxdataWindow.setChecked(isVisible)
-
- settings.endGroup()
-
- settings.beginGroup("content")
- isSorted = settings.value("is-sorted", True)
- try:
- if not isinstance(isSorted, bool):
- isSorted = utils.stringToBool(isSorted)
- except ValueError:
- isSorted = True
- self.setContentSorted(isSorted)
- settings.endGroup()
-
- if not pos.isNull():
- self.move(pos)
- if not size.isNull():
- self.resize(size)
- if isFullScreen:
- self.showFullScreen()
-
- def createActions(self):
- action = qt.QAction("E&xit", self)
- action.setShortcuts(qt.QKeySequence.Quit)
- action.setStatusTip("Exit the application")
- action.triggered.connect(self.close)
- self._exitAction = action
-
- action = qt.QAction("&Open...", self)
- action.setStatusTip("Open a file")
- action.triggered.connect(self.open)
- self._openAction = action
-
- action = qt.QAction("Open Recent", self)
- action.setStatusTip("Open a recently openned file")
- action.triggered.connect(self.open)
- self._openRecentAction = action
-
- action = qt.QAction("Close All", self)
- action.setStatusTip("Close all opened files")
- action.triggered.connect(self.closeAll)
- self._closeAllAction = action
-
- action = qt.QAction("&About", self)
- action.setStatusTip("Show the application's About box")
- action.triggered.connect(self.about)
- self._aboutAction = action
-
- action = qt.QAction("&Documentation", self)
- action.setStatusTip("Show the Silx library's documentation")
- action.triggered.connect(self.showDocumentation)
- self._documentationAction = action
-
- # Plot backend
-
- action = qt.QAction("Plot rendering backend", self)
- action.setStatusTip("Select plot rendering backend")
- self._plotBackendSelection = action
-
- menu = qt.QMenu()
- action.setMenu(menu)
- group = qt.QActionGroup(self)
- group.setExclusive(True)
-
- action = qt.QAction("matplotlib", self)
- action.setStatusTip("Plot will be rendered using matplotlib")
- action.setCheckable(True)
- action.triggered.connect(self.__forceMatplotlibBackend)
- group.addAction(action)
- menu.addAction(action)
- self._usePlotWithMatplotlib = action
-
- action = qt.QAction("OpenGL", self)
- action.setStatusTip("Plot will be rendered using OpenGL")
- action.setCheckable(True)
- action.triggered.connect(self.__forceOpenglBackend)
- group.addAction(action)
- menu.addAction(action)
- self._usePlotWithOpengl = action
-
- # Plot image orientation
-
- action = qt.QAction("Default plot image y-axis orientation", self)
- action.setStatusTip("Select the default y-axis orientation used by plot displaying images")
- self._plotImageOrientation = action
-
- menu = qt.QMenu()
- action.setMenu(menu)
- group = qt.QActionGroup(self)
- group.setExclusive(True)
-
- action = qt.QAction("Downward, origin on top", self)
- action.setIcon(self._iconDownward)
- action.setStatusTip("Plot images will use a downward Y-axis orientation")
- action.setCheckable(True)
- action.triggered.connect(self.__forcePlotImageDownward)
- group.addAction(action)
- menu.addAction(action)
- self._useYAxisOrientationDownward = action
-
- action = qt.QAction("Upward, origin on bottom", self)
- action.setIcon(self._iconUpward)
- action.setStatusTip("Plot images will use a upward Y-axis orientation")
- action.setCheckable(True)
- action.triggered.connect(self.__forcePlotImageUpward)
- group.addAction(action)
- menu.addAction(action)
- self._useYAxisOrientationUpward = action
-
- # Windows
-
- action = qt.QAction("Show custom NXdata selector", self)
- action.setStatusTip("Show a widget which allow to create plot by selecting data and axes")
- action.setCheckable(True)
- action.setShortcut(qt.QKeySequence(qt.Qt.Key_F6))
- action.toggled.connect(self.__toggleCustomNxdataWindow)
- self._displayCustomNxdataWindow = action
-
- def __toggleCustomNxdataWindow(self):
- isVisible = self._displayCustomNxdataWindow.isChecked()
- self.__customNxdataWindow.setVisible(isVisible)
-
- def __updateFileMenu(self):
- files = self.__context.getRecentFiles()
- self._openRecentAction.setEnabled(len(files) != 0)
- menu = None
- if len(files) != 0:
- menu = qt.QMenu()
- for filePath in files:
- baseName = os.path.basename(filePath)
- action = qt.QAction(baseName, self)
- action.setToolTip(filePath)
- action.triggered.connect(functools.partial(self.__openRecentFile, filePath))
- menu.addAction(action)
- menu.addSeparator()
- baseName = os.path.basename(filePath)
- action = qt.QAction("Clear history", self)
- action.setToolTip("Clear the history of the recent files")
- action.triggered.connect(self.__clearRecentFile)
- menu.addAction(action)
- self._openRecentAction.setMenu(menu)
-
- def __clearRecentFile(self):
- self.__context.clearRencentFiles()
-
- def __openRecentFile(self, filePath):
- self.appendFile(filePath)
-
- def __updateOptionMenu(self):
- """Update the state of the checked options as it is based on global
- environment values."""
-
- # plot backend
-
- action = self._plotBackendSelection
- title = action.text().split(": ", 1)[0]
- action.setText("%s: %s" % (title, silx.config.DEFAULT_PLOT_BACKEND))
-
- action = self._usePlotWithMatplotlib
- action.setChecked(silx.config.DEFAULT_PLOT_BACKEND in ["matplotlib", "mpl"])
- title = action.text().split(" (", 1)[0]
- if not action.isChecked():
- title += " (applied after application restart)"
- action.setText(title)
-
- action = self._usePlotWithOpengl
- action.setChecked(silx.config.DEFAULT_PLOT_BACKEND in ["opengl", "gl"])
- title = action.text().split(" (", 1)[0]
- if not action.isChecked():
- title += " (applied after application restart)"
- action.setText(title)
-
- # plot orientation
-
- action = self._plotImageOrientation
- if silx.config.DEFAULT_PLOT_IMAGE_Y_AXIS_ORIENTATION == "downward":
- action.setIcon(self._iconDownward)
- else:
- action.setIcon(self._iconUpward)
- action.setIconVisibleInMenu(True)
-
- action = self._useYAxisOrientationDownward
- action.setChecked(silx.config.DEFAULT_PLOT_IMAGE_Y_AXIS_ORIENTATION == "downward")
- title = action.text().split(" (", 1)[0]
- if not action.isChecked():
- title += " (applied after application restart)"
- action.setText(title)
-
- action = self._useYAxisOrientationUpward
- action.setChecked(silx.config.DEFAULT_PLOT_IMAGE_Y_AXIS_ORIENTATION != "downward")
- title = action.text().split(" (", 1)[0]
- if not action.isChecked():
- title += " (applied after application restart)"
- action.setText(title)
-
- def createMenus(self):
- fileMenu = self.menuBar().addMenu("&File")
- fileMenu.addAction(self._openAction)
- fileMenu.addAction(self._openRecentAction)
- fileMenu.addAction(self._closeAllAction)
- fileMenu.addSeparator()
- fileMenu.addAction(self._exitAction)
- fileMenu.aboutToShow.connect(self.__updateFileMenu)
-
- optionMenu = self.menuBar().addMenu("&Options")
- optionMenu.addAction(self._plotImageOrientation)
- optionMenu.addAction(self._plotBackendSelection)
- optionMenu.aboutToShow.connect(self.__updateOptionMenu)
-
- viewMenu = self.menuBar().addMenu("&Views")
- viewMenu.addAction(self._displayCustomNxdataWindow)
-
- helpMenu = self.menuBar().addMenu("&Help")
- helpMenu.addAction(self._aboutAction)
- helpMenu.addAction(self._documentationAction)
-
- def open(self):
- dialog = self.createFileDialog()
- if self.__dialogState is None:
- currentDirectory = os.getcwd()
- dialog.setDirectory(currentDirectory)
- else:
- dialog.restoreState(self.__dialogState)
-
- result = dialog.exec_()
- if not result:
- return
-
- self.__dialogState = dialog.saveState()
-
- filenames = dialog.selectedFiles()
- for filename in filenames:
- self.appendFile(filename)
-
- def closeAll(self):
- """Close all currently opened files"""
- model = self.__treeview.findHdf5TreeModel()
- model.clear()
-
- def createFileDialog(self):
- dialog = qt.QFileDialog(self)
- dialog.setWindowTitle("Open")
- dialog.setModal(True)
-
- # NOTE: hdf5plugin have to be loaded before
- extensions = collections.OrderedDict()
- for description, ext in silx.io.supported_extensions().items():
- extensions[description] = " ".join(sorted(list(ext)))
-
- # Add extensions supported by fabio
- 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"
-
- all_supported_extensions = set()
- for name, exts in extensions.items():
- exts = exts.split(" ")
- all_supported_extensions.update(exts)
- all_supported_extensions = sorted(list(all_supported_extensions))
-
- filters = []
- filters.append("All supported files (%s)" % " ".join(all_supported_extensions))
- for name, extension in extensions.items():
- filters.append("%s (%s)" % (name, extension))
- filters.append("All files (*)")
-
- dialog.setNameFilters(filters)
- dialog.setFileMode(qt.QFileDialog.ExistingFiles)
- return dialog
-
- def about(self):
- from .About import About
- About.about(self, "Silx viewer")
-
- def showDocumentation(self):
- subpath = "index.html"
- url = projecturl.getDocumentationUrl(subpath)
- qt.QDesktopServices.openUrl(qt.QUrl(url))
-
- def setContentSorted(self, sort):
- """Set whether file content should be sorted or not.
-
- :param bool sort:
- """
- sort = bool(sort)
- if sort != self.isContentSorted():
-
- # save expanded nodes
- pathss = []
- root = qt.QModelIndex()
- model = self.__treeview.model()
- for i in range(model.rowCount(root)):
- index = model.index(i, 0, root)
- paths = self.__getPathFromExpandedNodes(self.__treeview, index)
- pathss.append(paths)
-
- self.__treeview.setModel(
- self.__treeModelSorted if sort else self.__treeModelSorted.sourceModel())
- self._sortContentAction.setChecked(self.isContentSorted())
-
- # restore expanded nodes
- model = self.__treeview.model()
- for i in range(model.rowCount(root)):
- index = model.index(i, 0, root)
- paths = pathss.pop(0)
- self.__expandNodesFromPaths(self.__treeview, index, paths)
-
- def isContentSorted(self):
- """Returns whether the file content is sorted or not.
-
- :rtype: bool
- """
- return self.__treeview.model() is self.__treeModelSorted
-
- def __forcePlotImageDownward(self):
- silx.config.DEFAULT_PLOT_IMAGE_Y_AXIS_ORIENTATION = "downward"
-
- def __forcePlotImageUpward(self):
- silx.config.DEFAULT_PLOT_IMAGE_Y_AXIS_ORIENTATION = "upward"
-
- def __forceMatplotlibBackend(self):
- silx.config.DEFAULT_PLOT_BACKEND = "matplotlib"
-
- def __forceOpenglBackend(self):
- silx.config.DEFAULT_PLOT_BACKEND = "opengl"
-
- def appendFile(self, filename):
- if self.__displayIt is None:
- # Store the file to display it (loading could be async)
- self.__displayIt = filename
- self.__treeview.findHdf5TreeModel().appendFile(filename)
-
- def displaySelectedData(self):
- """Called to update the dataviewer with the selected data.
- """
- selected = list(self.__treeview.selectedH5Nodes(ignoreBrokenLinks=False))
- if len(selected) == 1:
- # Update the viewer for a single selection
- data = selected[0]
- self.__dataPanel.setData(data)
- else:
- _logger.debug("Too many data selected")
-
- def displayData(self, data):
- """Called to update the dataviewer with a secific data.
- """
- self.__dataPanel.setData(data)
-
- def displaySelectedCustomData(self):
- selected = list(self.__customNxdata.selectedItems())
- if len(selected) == 1:
- # Update the viewer for a single selection
- item = selected[0]
- self.__dataPanel.setCustomDataItem(item)
- else:
- _logger.debug("Too many items selected")
-
- def __customNxdataRemoved(self, item):
- if self.__dataPanel.getCustomNxdataItem() is item:
- self.__dataPanel.setCustomDataItem(None)
-
- def __customNxdataUpdated(self, item):
- if self.__dataPanel.getCustomNxdataItem() is item:
- self.__dataPanel.setCustomDataItem(item)
-
- def __makeSureCustomNxDataWindowIsVisible(self):
- if not self.__customNxdataWindow.isVisible():
- self.__customNxdataWindow.setVisible(True)
- self._displayCustomNxdataWindow.setChecked(True)
-
- def useAsNewCustomSignal(self, h5dataset):
- self.__makeSureCustomNxDataWindowIsVisible()
- model = self.__customNxdata.model()
- model.createFromSignal(h5dataset)
-
- def useAsNewCustomNxdata(self, h5nxdata):
- self.__makeSureCustomNxDataWindowIsVisible()
- model = self.__customNxdata.model()
- model.createFromNxdata(h5nxdata)
-
- 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(ignoreBrokenLinks=False)
- menu = event.menu()
-
- if not menu.isEmpty():
- menu.addSeparator()
-
- for obj in selectedObjects:
- h5 = obj.h5py_object
-
- name = obj.name
- if name.startswith("/"):
- name = name[1:]
- if name == "":
- name = "the root"
-
- action = qt.QAction("Show %s" % name, event.source())
- action.triggered.connect(lambda: self.displayData(h5))
- menu.addAction(action)
-
- if silx.io.is_dataset(h5):
- action = qt.QAction("Use as a new custom signal", event.source())
- action.triggered.connect(lambda: self.useAsNewCustomSignal(h5))
- menu.addAction(action)
-
- if silx.io.is_group(h5) and silx.io.nxdata.is_valid_nxdata(h5):
- action = qt.QAction("Use as a new custom NXdata", event.source())
- action.triggered.connect(lambda: self.useAsNewCustomNxdata(h5))
- menu.addAction(action)
-
- if silx.io.is_file(h5):
- action = qt.QAction("Close %s" % obj.local_filename, event.source())
- action.triggered.connect(lambda: self.__treeview.findHdf5TreeModel().removeH5pyObject(h5))
- menu.addAction(action)
- action = qt.QAction("Synchronize %s" % obj.local_filename, event.source())
- action.triggered.connect(lambda: self.__synchronizeH5pyObject(h5))
- menu.addAction(action)
diff --git a/silx/app/view/__init__.py b/silx/app/view/__init__.py
deleted file mode 100644
index 229c44e..0000000
--- a/silx/app/view/__init__.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-# Copyright (C) 2016-2018 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ############################################################################*/
-"""Package containing source code of the `silx view` application"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "07/06/2018"
diff --git a/silx/app/view/main.py b/silx/app/view/main.py
deleted file mode 100644
index a1369c1..0000000
--- a/silx/app/view/main.py
+++ /dev/null
@@ -1,171 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-# Copyright (C) 2016-2020 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 containing launcher of the `silx view` application"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "17/01/2019"
-
-import argparse
-import logging
-import os
-import signal
-import sys
-
-
-_logger = logging.getLogger(__name__)
-"""Module logger"""
-
-
-def createParser():
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument(
- 'files',
- 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)')
- parser.add_argument(
- '-f', '--fresh',
- dest="fresh_preferences",
- action="store_true",
- default=False,
- help='Start the application using new fresh user preferences')
- parser.add_argument(
- '--hdf5-file-locking',
- dest="hdf5_file_locking",
- action="store_true",
- default=False,
- help='Start the application with HDF5 file locking enabled (it is disabled by default)')
- return parser
-
-
-def createWindow(parent, settings):
- from .Viewer import Viewer
- window = Viewer(parent=None, settings=settings)
- return window
-
-
-def mainQt(options):
- """Part of the main depending on Qt"""
- if options.debug:
- logging.root.setLevel(logging.DEBUG)
-
- #
- # Import most of the things here to be sure to use the right logging level
- #
-
- # This needs to be done prior to load HDF5
- hdf5_file_locking = 'TRUE' if options.hdf5_file_locking else 'FALSE'
- _logger.info('Set HDF5_USE_FILE_LOCKING=%s', hdf5_file_locking)
- os.environ['HDF5_USE_FILE_LOCKING'] = hdf5_file_locking
-
- try:
- # it should be loaded before h5py
- import hdf5plugin # noqa
- except ImportError:
- _logger.debug("Backtrace", exc_info=True)
-
- import h5py
-
- import silx
- import silx.utils.files
- from silx.gui import qt
- # Make sure matplotlib is configured
- # Needed for Debian 8: compatibility between Qt4/Qt5 and old matplotlib
- import silx.gui.utils.matplotlib # noqa
-
- app = qt.QApplication([])
- qt.QLocale.setDefault(qt.QLocale.c())
-
- def sigintHandler(*args):
- """Handler for the SIGINT signal."""
- qt.QApplication.quit()
-
- signal.signal(signal.SIGINT, sigintHandler)
- sys.excepthook = qt.exceptionHandler
-
- timer = qt.QTimer()
- timer.start(500)
- # Application have to wake up Python interpreter, else SIGINT is not
- # catched
- timer.timeout.connect(lambda: None)
-
- settings = qt.QSettings(qt.QSettings.IniFormat,
- qt.QSettings.UserScope,
- "silx",
- "silx-view",
- None)
- if options.fresh_preferences:
- settings.clear()
-
- window = createWindow(parent=None, settings=settings)
- window.setAttribute(qt.Qt.WA_DeleteOnClose, True)
-
- if options.use_opengl_plot:
- # It have to be done after the settings (after the Viewer creation)
- silx.config.DEFAULT_PLOT_BACKEND = "opengl"
-
- # NOTE: under Windows, cmd does not convert `*.tif` into existing files
- options.files = silx.utils.files.expand_filenames(options.files)
-
- for filename in options.files:
- # TODO: Would be nice to add a process widget and a cancel button
- try:
- window.appendFile(filename)
- except IOError as e:
- _logger.error(e.args[0])
- _logger.debug("Backtrace", exc_info=True)
-
- window.show()
- result = app.exec_()
- # remove ending warnings relative to QTimer
- app.deleteLater()
- return result
-
-
-def main(argv):
- """
- Main function to launch the viewer as an application
-
- :param argv: Command line arguments
- :returns: exit status
- """
- parser = createParser()
- options = parser.parse_args(argv[1:])
- mainQt(options)
-
-
-if __name__ == '__main__':
- main(sys.argv)
diff --git a/silx/app/view/setup.py b/silx/app/view/setup.py
deleted file mode 100644
index fa076cb..0000000
--- a/silx/app/view/setup.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# 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.
-#
-# ############################################################################*/
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "06/06/2018"
-
-from numpy.distutils.misc_util import Configuration
-
-
-def configuration(parent_package='', top_path=None):
- config = Configuration('view', parent_package, top_path)
- config.add_subpackage('test')
- return config
-
-
-if __name__ == "__main__":
- from numpy.distutils.core import setup
- setup(configuration=configuration)
diff --git a/silx/app/view/test/__init__.py b/silx/app/view/test/__init__.py
deleted file mode 100644
index 8e64948..0000000
--- a/silx/app/view/test/__init__.py
+++ /dev/null
@@ -1,41 +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.
-#
-# ###########################################################################*/
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "07/06/2018"
-
-import unittest
-
-from silx.test.utils import test_options
-
-
-def suite():
- test_suite = unittest.TestSuite()
- if test_options.WITH_QT_TEST:
- from . import test_launcher
- from . import test_view
- test_suite.addTest(test_view.suite())
- test_suite.addTest(test_launcher.suite())
- return test_suite
diff --git a/silx/app/view/test/test_launcher.py b/silx/app/view/test/test_launcher.py
deleted file mode 100644
index 5f03de9..0000000
--- a/silx/app/view/test/test_launcher.py
+++ /dev/null
@@ -1,151 +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.
-#
-# ###########################################################################*/
-"""Module testing silx.app.view"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "07/06/2018"
-
-
-import os
-import shutil
-import sys
-import tempfile
-import unittest
-import logging
-import subprocess
-
-from silx.test.utils import test_options
-from .. import main
-from silx import __main__ as silx_main
-
-_logger = logging.getLogger(__name__)
-
-
-@unittest.skipUnless(test_options.WITH_QT_TEST, test_options.WITH_QT_TEST_REASON)
-class TestLauncher(unittest.TestCase):
- """Test command line parsing"""
-
- def testHelp(self):
- # option -h must cause a raise SystemExit or a return 0
- try:
- parser = main.createParser()
- parser.parse_args(["view", "--help"])
- result = 0
- except SystemExit as e:
- result = e.args[0]
- self.assertEqual(result, 0)
-
- def testWrongOption(self):
- try:
- parser = main.createParser()
- parser.parse_args(["view", "--foo"])
- self.fail()
- except SystemExit as e:
- result = e.args[0]
- self.assertNotEqual(result, 0)
-
- def testWrongFile(self):
- try:
- parser = main.createParser()
- result = parser.parse_args(["view", "__file.not.found__"])
- result = 0
- except SystemExit as e:
- result = e.args[0]
- self.assertEqual(result, 0)
-
- def executeAsScript(self, filename, *args):
- """Execute a command line.
-
- Log output as debug in case of bad return code.
- """
- env = self.createTestEnv()
-
- with tempfile.TemporaryDirectory() as tmpdir:
- # Copy file to temporary dir to avoid import from current dir.
- script = os.path.join(tmpdir, 'launcher.py')
- shutil.copyfile(filename, script)
- command_line = [sys.executable, script] + list(args)
-
- _logger.info("Execute: %s", " ".join(command_line))
- p = subprocess.Popen(command_line,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- env=env)
- out, err = p.communicate()
- _logger.info("Return code: %d", p.returncode)
- try:
- out = out.decode('utf-8')
- except UnicodeError:
- pass
- try:
- err = err.decode('utf-8')
- except UnicodeError:
- pass
-
- if p.returncode != 0:
- _logger.info("stdout:")
- _logger.info("%s", out)
- _logger.info("stderr:")
- _logger.info("%s", err)
- else:
- _logger.debug("stdout:")
- _logger.debug("%s", out)
- _logger.debug("stderr:")
- _logger.debug("%s", err)
- self.assertEqual(p.returncode, 0)
-
- def createTestEnv(self):
- """
- Returns an associated environment with a working project.
- """
- env = dict((str(k), str(v)) for k, v in os.environ.items())
- env["PYTHONPATH"] = os.pathsep.join(sys.path)
- return env
-
- def testExecuteViewHelp(self):
- """Test if the main module is well connected.
-
- Uses subprocess to avoid to parasite the current environment.
- """
- self.executeAsScript(main.__file__, "--help")
-
- def testExecuteSilxViewHelp(self):
- """Test if the main module is well connected.
-
- Uses subprocess to avoid to parasite the current environment.
- """
- self.executeAsScript(silx_main.__file__, "view", "--help")
-
-
-def suite():
- test_suite = unittest.TestSuite()
- loader = unittest.defaultTestLoader.loadTestsFromTestCase
- test_suite.addTest(loader(TestLauncher))
- return test_suite
-
-
-if __name__ == '__main__':
- unittest.main(defaultTest='suite')
diff --git a/silx/app/view/test/test_view.py b/silx/app/view/test/test_view.py
deleted file mode 100644
index 7ea5a2c..0000000
--- a/silx/app/view/test/test_view.py
+++ /dev/null
@@ -1,394 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016-2020 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.view"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "07/06/2018"
-
-
-import unittest
-import weakref
-import numpy
-import tempfile
-import shutil
-import os.path
-import h5py
-
-from silx.gui import qt
-from silx.app.view.Viewer import Viewer
-from silx.app.view.About import About
-from silx.app.view.DataPanel import DataPanel
-from silx.app.view.CustomNxdataWidget import CustomNxdataWidget
-from silx.gui.hdf5._utils import Hdf5DatasetMimeData
-from silx.gui.utils.testutils import TestCaseQt
-from silx.io import commonh5
-
-_tmpDirectory = None
-
-
-def setUpModule():
- global _tmpDirectory
- _tmpDirectory = tempfile.mkdtemp(prefix=__name__)
-
- # create h5 data
- filename = _tmpDirectory + "/data.h5"
- f = h5py.File(filename, "w")
- g = f.create_group("arrays")
- g.create_dataset("scalar", data=10)
- g.create_dataset("integers", data=numpy.array([10, 20, 30]))
- f.close()
-
- # create h5 data
- filename = _tmpDirectory + "/data2.h5"
- f = h5py.File(filename, "w")
- g = f.create_group("arrays")
- g.create_dataset("scalar", data=20)
- g.create_dataset("integers", data=numpy.array([10, 20, 30]))
- f.close()
-
-
-def tearDownModule():
- global _tmpDirectory
- shutil.rmtree(_tmpDirectory)
- _tmpDirectory = None
-
-
-class TestViewer(TestCaseQt):
- """Test for Viewer class"""
-
- def testConstruct(self):
- widget = Viewer()
- self.qWaitForWindowExposed(widget)
-
- def testDestroy(self):
- widget = Viewer()
- ref = weakref.ref(widget)
- widget = None
- self.qWaitForDestroy(ref)
-
-
-class TestAbout(TestCaseQt):
- """Test for About box class"""
-
- def testConstruct(self):
- widget = About()
- self.qWaitForWindowExposed(widget)
-
- def testLicense(self):
- widget = About()
- widget.getHtmlLicense()
- self.qWaitForWindowExposed(widget)
-
- def testDestroy(self):
- widget = About()
- ref = weakref.ref(widget)
- widget = None
- self.qWaitForDestroy(ref)
-
-
-class TestDataPanel(TestCaseQt):
-
- def testConstruct(self):
- widget = DataPanel()
- self.qWaitForWindowExposed(widget)
-
- def testDestroy(self):
- widget = DataPanel()
- ref = weakref.ref(widget)
- widget = None
- self.qWaitForDestroy(ref)
-
- def testHeaderLabelPaintEvent(self):
- widget = DataPanel()
- data = numpy.array([1, 2, 3, 4, 5])
- widget.setData(data)
- # Expected to execute HeaderLabel.paintEvent
- widget.setVisible(True)
- self.qWaitForWindowExposed(widget)
-
- def testData(self):
- widget = DataPanel()
- data = numpy.array([1, 2, 3, 4, 5])
- widget.setData(data)
- self.assertIs(widget.getData(), data)
- self.assertIs(widget.getCustomNxdataItem(), None)
-
- def testDataNone(self):
- widget = DataPanel()
- widget.setData(None)
- self.assertIs(widget.getData(), None)
- self.assertIs(widget.getCustomNxdataItem(), None)
-
- def testCustomDataItem(self):
- class CustomDataItemMock(object):
- def getVirtualGroup(self):
- return None
-
- def text(self):
- return ""
-
- data = CustomDataItemMock()
- widget = DataPanel()
- widget.setCustomDataItem(data)
- self.assertIs(widget.getData(), None)
- self.assertIs(widget.getCustomNxdataItem(), data)
-
- def testCustomDataItemNone(self):
- data = None
- widget = DataPanel()
- widget.setCustomDataItem(data)
- self.assertIs(widget.getData(), None)
- self.assertIs(widget.getCustomNxdataItem(), data)
-
- def testRemoveDatasetsFrom(self):
- f = h5py.File(os.path.join(_tmpDirectory, "data.h5"), mode='r')
- try:
- widget = DataPanel()
- widget.setData(f["arrays/scalar"])
- widget.removeDatasetsFrom(f)
- self.assertIs(widget.getData(), None)
- finally:
- widget.setData(None)
- f.close()
-
- def testReplaceDatasetsFrom(self):
- f = h5py.File(os.path.join(_tmpDirectory, "data.h5"), mode='r')
- f2 = h5py.File(os.path.join(_tmpDirectory, "data2.h5"), mode='r')
- try:
- widget = DataPanel()
- widget.setData(f["arrays/scalar"])
- self.assertEqual(widget.getData()[()], 10)
- widget.replaceDatasetsFrom(f, f2)
- self.assertEqual(widget.getData()[()], 20)
- finally:
- widget.setData(None)
- f.close()
- f2.close()
-
-
-class TestCustomNxdataWidget(TestCaseQt):
-
- def testConstruct(self):
- widget = CustomNxdataWidget()
- self.qWaitForWindowExposed(widget)
-
- def testDestroy(self):
- widget = CustomNxdataWidget()
- ref = weakref.ref(widget)
- widget = None
- self.qWaitForDestroy(ref)
-
- def testCreateNxdata(self):
- widget = CustomNxdataWidget()
- model = widget.model()
- model.createNewNxdata()
- model.createNewNxdata("Foo")
- widget.setVisible(True)
- self.qWaitForWindowExposed(widget)
-
- def testCreateNxdataFromDataset(self):
- widget = CustomNxdataWidget()
- model = widget.model()
- signal = commonh5.Dataset("foo", data=numpy.array([[[5]]]))
- model.createFromSignal(signal)
- widget.setVisible(True)
- self.qWaitForWindowExposed(widget)
-
- def testCreateNxdataFromNxdata(self):
- widget = CustomNxdataWidget()
- model = widget.model()
- data = numpy.array([[[5]]])
- nxdata = commonh5.Group("foo")
- nxdata.attrs["NX_class"] = "NXdata"
- nxdata.attrs["signal"] = "signal"
- nxdata.create_dataset("signal", data=data)
- model.createFromNxdata(nxdata)
- widget.setVisible(True)
- self.qWaitForWindowExposed(widget)
-
- def testCreateBadNxdata(self):
- widget = CustomNxdataWidget()
- model = widget.model()
- signal = commonh5.Dataset("foo", data=numpy.array([[[5]]]))
- model.createFromSignal(signal)
- axis = commonh5.Dataset("foo", data=numpy.array([[[5]]]))
- nxdataIndex = model.index(0, 0)
- item = model.itemFromIndex(nxdataIndex)
- item.setAxesDatasets([axis])
- nxdata = item.getVirtualGroup()
- self.assertIsNotNone(nxdata)
- self.assertFalse(item.isValid())
-
- def testRemoveDatasetsFrom(self):
- f = h5py.File(os.path.join(_tmpDirectory, "data.h5"), mode='r')
- try:
- widget = CustomNxdataWidget()
- model = widget.model()
- dataset = f["arrays/integers"]
- model.createFromSignal(dataset)
- widget.removeDatasetsFrom(f)
- finally:
- model.clear()
- f.close()
-
- def testReplaceDatasetsFrom(self):
- f = h5py.File(os.path.join(_tmpDirectory, "data.h5"), mode='r')
- f2 = h5py.File(os.path.join(_tmpDirectory, "data2.h5"), mode='r')
- try:
- widget = CustomNxdataWidget()
- model = widget.model()
- dataset = f["arrays/integers"]
- model.createFromSignal(dataset)
- widget.replaceDatasetsFrom(f, f2)
- finally:
- model.clear()
- f.close()
- f2.close()
-
-
-class TestCustomNxdataWidgetInteraction(TestCaseQt):
- """Test CustomNxdataWidget with user interaction"""
-
- def setUp(self):
- TestCaseQt.setUp(self)
-
- self.widget = CustomNxdataWidget()
- self.model = self.widget.model()
- data = numpy.array([[[5]]])
- dataset = commonh5.Dataset("foo", data=data)
- self.model.createFromSignal(dataset)
- self.selectionModel = self.widget.selectionModel()
-
- def tearDown(self):
- self.selectionModel = None
- self.model.clear()
- self.model = None
- self.widget = None
- TestCaseQt.tearDown(self)
-
- def testSelectedNxdata(self):
- index = self.model.index(0, 0)
- self.selectionModel.setCurrentIndex(index, qt.QItemSelectionModel.ClearAndSelect)
- nxdata = self.widget.selectedNxdata()
- self.assertEqual(len(nxdata), 1)
- self.assertIsNot(nxdata[0], None)
-
- def testSelectedItems(self):
- index = self.model.index(0, 0)
- self.selectionModel.setCurrentIndex(index, qt.QItemSelectionModel.ClearAndSelect)
- items = self.widget.selectedItems()
- self.assertEqual(len(items), 1)
- self.assertIsNot(items[0], None)
- self.assertIsInstance(items[0], qt.QStandardItem)
-
- def testRowsAboutToBeRemoved(self):
- self.model.removeRow(0)
- self.qWaitForWindowExposed(self.widget)
-
- def testPaintItems(self):
- self.widget.expandAll()
- self.widget.setVisible(True)
- self.qWaitForWindowExposed(self.widget)
-
- def testCreateDefaultContextMenu(self):
- nxDataIndex = self.model.index(0, 0)
- menu = self.widget.createDefaultContextMenu(nxDataIndex)
- self.assertIsNot(menu, None)
- self.assertIsInstance(menu, qt.QMenu)
-
- signalIndex = self.model.index(0, 0, nxDataIndex)
- menu = self.widget.createDefaultContextMenu(signalIndex)
- self.assertIsNot(menu, None)
- self.assertIsInstance(menu, qt.QMenu)
-
- axesIndex = self.model.index(1, 0, nxDataIndex)
- menu = self.widget.createDefaultContextMenu(axesIndex)
- self.assertIsNot(menu, None)
- self.assertIsInstance(menu, qt.QMenu)
-
- def testDropNewDataset(self):
- dataset = commonh5.Dataset("foo", numpy.array([1, 2, 3, 4]))
- mimedata = Hdf5DatasetMimeData(dataset=dataset)
- self.model.dropMimeData(mimedata, qt.Qt.CopyAction, -1, -1, qt.QModelIndex())
- self.assertEqual(self.model.rowCount(qt.QModelIndex()), 2)
-
- def testDropNewNxdata(self):
- data = numpy.array([[[5]]])
- nxdata = commonh5.Group("foo")
- nxdata.attrs["NX_class"] = "NXdata"
- nxdata.attrs["signal"] = "signal"
- nxdata.create_dataset("signal", data=data)
- mimedata = Hdf5DatasetMimeData(dataset=nxdata)
- self.model.dropMimeData(mimedata, qt.Qt.CopyAction, -1, -1, qt.QModelIndex())
- self.assertEqual(self.model.rowCount(qt.QModelIndex()), 2)
-
- def testDropAxisDataset(self):
- dataset = commonh5.Dataset("foo", numpy.array([1, 2, 3, 4]))
- mimedata = Hdf5DatasetMimeData(dataset=dataset)
- nxDataIndex = self.model.index(0, 0)
- axesIndex = self.model.index(1, 0, nxDataIndex)
- self.model.dropMimeData(mimedata, qt.Qt.CopyAction, -1, -1, axesIndex)
- self.assertEqual(self.model.rowCount(qt.QModelIndex()), 1)
- item = self.model.itemFromIndex(axesIndex)
- self.assertIsNot(item.getDataset(), None)
-
- def testMimeData(self):
- nxDataIndex = self.model.index(0, 0)
- signalIndex = self.model.index(0, 0, nxDataIndex)
- mimeData = self.model.mimeData([signalIndex])
- self.assertIsNot(mimeData, None)
- self.assertIsInstance(mimeData, qt.QMimeData)
-
- def testRemoveNxdataItem(self):
- nxdataIndex = self.model.index(0, 0)
- item = self.model.itemFromIndex(nxdataIndex)
- self.model.removeNxdataItem(item)
-
- def testAppendAxisToNxdataItem(self):
- nxdataIndex = self.model.index(0, 0)
- item = self.model.itemFromIndex(nxdataIndex)
- self.model.appendAxisToNxdataItem(item)
-
- def testRemoveAxisItem(self):
- nxdataIndex = self.model.index(0, 0)
- axesIndex = self.model.index(1, 0, nxdataIndex)
- item = self.model.itemFromIndex(axesIndex)
- self.model.removeAxisItem(item)
-
-
-def suite():
- test_suite = unittest.TestSuite()
- loader = unittest.defaultTestLoader.loadTestsFromTestCase
- test_suite.addTest(loader(TestViewer))
- test_suite.addTest(loader(TestAbout))
- test_suite.addTest(loader(TestDataPanel))
- test_suite.addTest(loader(TestCustomNxdataWidget))
- test_suite.addTest(loader(TestCustomNxdataWidgetInteraction))
- return test_suite
-
-
-if __name__ == '__main__':
- unittest.main(defaultTest='suite')
diff --git a/silx/app/view/utils.py b/silx/app/view/utils.py
deleted file mode 100644
index 80167c8..0000000
--- a/silx/app/view/utils.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-# Copyright (C) 2018 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ############################################################################*/
-"""Browse a data file with a GUI"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "28/05/2018"
-
-
-_trueStrings = set(["yes", "true", "1"])
-_falseStrings = set(["no", "false", "0"])
-
-
-def stringToBool(string):
- """Returns a boolean from a string.
-
- :raise ValueError: If the string do not contains a boolean information.
- """
- lower = string.lower()
- if lower in _trueStrings:
- return True
- if lower in _falseStrings:
- return False
- raise ValueError("'%s' is not a valid boolean" % string)
diff --git a/silx/gui/__init__.py b/silx/gui/__init__.py
deleted file mode 100644
index b796e20..0000000
--- a/silx/gui/__init__.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016-2018 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""This package provides a set of Qt widgets.
-
-It contains the following sub-packages and modules:
-
-- silx.gui.colors: Functions to handle colors and colormap
-- silx.gui.console: IPython console widget
-- silx.gui.data:
- Widgets for displaying data arrays using table views and plot widgets
-- silx.gui.dialog: Specific dialog widgets
-- silx.gui.fit: Widgets for controlling curve fitting
-- silx.gui.hdf5: Widgets for displaying content relative to HDF5 format
-- silx.gui.icons: Functions to access embedded icons
-- silx.gui.plot: Widgets for 1D and 2D plotting and related tools
-- silx.gui.plot3d: Widgets for visualizing data in 3D based on OpenGL
-- silx.gui.printer: Shared printer used by the library
-- silx.gui.qt: Common wrapper over different Python Qt binding
-- silx.gui.utils: Miscellaneous helpers for Qt
-- silx.gui.widgets: Miscellaneous standalone widgets
-
-See silx documentation: http://www.silx.org/doc/silx/latest/
-"""
-
-__authors__ = ["T. Vincent"]
-__license__ = "MIT"
-__date__ = "23/05/2016"
diff --git a/silx/gui/_glutils/Context.py b/silx/gui/_glutils/Context.py
deleted file mode 100644
index c62dbb9..0000000
--- a/silx/gui/_glutils/Context.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2014-2019 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# 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.
-#
-# ###########################################################################*/
-"""Abstraction of OpenGL context.
-
-It defines a way to get current OpenGL context to support multiple
-OpenGL contexts.
-"""
-
-__authors__ = ["T. Vincent"]
-__license__ = "MIT"
-__date__ = "25/07/2016"
-
-import contextlib
-
-
-class _DEFAULT_CONTEXT(object):
- """The default value for OpenGL context"""
- pass
-
-_context = _DEFAULT_CONTEXT
-"""The current OpenGL context"""
-
-
-def getCurrent():
- """Returns platform dependent object of current OpenGL context.
-
- This is useful to associate OpenGL resources with the context they are
- created in.
-
- :return: Platform specific OpenGL context
- """
- return _context
-
-
-def setCurrent(context=_DEFAULT_CONTEXT):
- """Set a platform dependent OpenGL context
-
- :param context: Platform dependent GL context
- """
- global _context
- _context = context
-
-
-@contextlib.contextmanager
-def current(context):
- """Context manager setting the platform-dependent GL context
-
- :param context: Platform dependent GL context
- """
- previous_context = getCurrent()
- setCurrent(context)
- yield
- setCurrent(previous_context)
diff --git a/silx/gui/_glutils/FramebufferTexture.py b/silx/gui/_glutils/FramebufferTexture.py
deleted file mode 100644
index e065030..0000000
--- a/silx/gui/_glutils/FramebufferTexture.py
+++ /dev/null
@@ -1,165 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2014-2020 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.
-#
-# ###########################################################################*/
-"""Association of a texture and a framebuffer object for off-screen rendering.
-"""
-
-__authors__ = ["T. Vincent"]
-__license__ = "MIT"
-__date__ = "25/07/2016"
-
-
-import logging
-
-from . import gl
-from .Texture import Texture
-
-
-_logger = logging.getLogger(__name__)
-
-
-class FramebufferTexture(object):
- """Framebuffer with a texture.
-
- Aimed at off-screen rendering to texture.
-
- :param internalFormat: OpenGL texture internal format
- :param shape: Shape (height, width) of the framebuffer and texture
- :type shape: 2-tuple of int
- :param stencilFormat: Stencil renderbuffer format
- :param depthFormat: Depth renderbuffer format
- :param kwargs: Extra arguments for :class:`Texture` constructor
- """
-
- _PACKED_FORMAT = gl.GL_DEPTH24_STENCIL8, gl.GL_DEPTH_STENCIL
-
- def __init__(self,
- internalFormat,
- shape,
- stencilFormat=gl.GL_DEPTH24_STENCIL8,
- depthFormat=gl.GL_DEPTH24_STENCIL8,
- **kwargs):
-
- self._texture = Texture(internalFormat, shape=shape, **kwargs)
- self._texture.prepare()
-
- self._previousFramebuffer = 0 # Used by with statement
-
- self._name = gl.glGenFramebuffers(1)
-
- 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,
- 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)
- 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)
-
- @property
- def shape(self):
- """Shape of the framebuffer (height, width)"""
- return self._texture.shape
-
- @property
- def texture(self):
- """The texture this framebuffer is rendering to.
-
- The life-cycle of the texture is managed by this object"""
- return self._texture
-
- @property
- def name(self):
- """OpenGL name of the framebuffer"""
- if self._name is not None:
- return self._name
- else:
- raise RuntimeError("No OpenGL framebuffer resource, \
- discard has already been called")
-
- def bind(self):
- """Bind this framebuffer for rendering"""
- gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.name)
-
- # with statement
-
- def __enter__(self):
- self._previousFramebuffer = gl.glGetInteger(gl.GL_FRAMEBUFFER_BINDING)
- self.bind()
-
- 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"""
- if self._name is not None:
- gl.glDeleteFramebuffers(self._name)
- self._name = None
-
- if self._stencilId is not None:
- gl.glDeleteRenderbuffers(self._stencilId)
- if self._stencilId == self._depthId:
- self._depthId = None
- self._stencilId = None
- if self._depthId is not None:
- gl.glDeleteRenderbuffers(self._depthId)
- self._depthId = None
-
- self._texture.discard() # Also discard the texture
- else:
- _logger.warning("Discard has already been called")
diff --git a/silx/gui/_glutils/OpenGLWidget.py b/silx/gui/_glutils/OpenGLWidget.py
deleted file mode 100644
index 5e3fcb8..0000000
--- a/silx/gui/_glutils/OpenGLWidget.py
+++ /dev/null
@@ -1,423 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2017-2020 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__ = "22/11/2019"
-
-
-import logging
-import sys
-
-from .. import qt
-from ..utils.glutils import isOpenGLAvailable
-from .._glutils import gl
-
-
-_logger = logging.getLogger(__name__)
-
-
-if not hasattr(qt, 'QOpenGLWidget') and not hasattr(qt, 'QGLWidget'):
- OpenGLWidget = None
-
-else:
- if hasattr(qt, 'QOpenGLWidget'): # PyQt>=5.4
- _logger.info('Using QOpenGLWidget')
- _BaseOpenGLWidget = qt.QOpenGLWidget
-
- else:
- _logger.info('Using QGLWidget')
- _BaseOpenGLWidget = qt.QGLWidget
-
- 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_)
-
- # Enable receiving mouse move events when no buttons are pressed
- self.setMouseTracking(True)
-
- 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():
- try:
- gl.glGetError() # clear any previous error (if any)
- version = gl.glGetString(gl.GL_VERSION)
- except:
- version = None
-
- 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 in ('PyQt5', 'PySide2'):
- 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)
-
- self.__context = None
-
- _check = isOpenGLAvailable(version=version, runtimeCheck=False)
- if _OpenGLWidget is None or not _check:
- _logger.error('OpenGL-based widget disabled: %s', _check.error)
- self.__openGLWidget = None
- label = self._createErrorQLabel(_check.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 getDotsPerInch(self):
- """Returns current screen resolution as device pixels per inch.
-
- :rtype: float
- """
- screen = self.window().windowHandle().screen()
- if screen is not None:
- # TODO check if this is correct on different OS/screen
- # OK on macOS10.12/qt5.13.2
- dpi = screen.physicalDotsPerInch() * self.getDevicePixelRatio()
- else: # Fallback
- dpi = 96. * self.getDevicePixelRatio()
- return dpi
-
- 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:
- # Keep a reference on QOpenGLContext to make
- # else PyQt5 keeps creating a new one.
- self.__context = self.__openGLWidget.context()
- return self.__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/Program.py b/silx/gui/_glutils/Program.py
deleted file mode 100644
index 87eec5f..0000000
--- a/silx/gui/_glutils/Program.py
+++ /dev/null
@@ -1,202 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2014-2019 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""This module provides a class to handle shader program compilation."""
-
-__authors__ = ["T. Vincent"]
-__license__ = "MIT"
-__date__ = "25/07/2016"
-
-
-import logging
-import weakref
-
-import numpy
-
-from . import Context, gl
-
-_logger = logging.getLogger(__name__)
-
-
-class Program(object):
- """Wrap OpenGL shader program.
-
- The program is compiled lazily (i.e., at first program :meth:`use`).
- When the program is compiled, it stores attributes and uniforms locations.
- So, attributes and uniforms must be used after :meth:`use`.
-
- This object supports multiple OpenGL contexts.
-
- :param str vertexShader: The source of the vertex shader.
- :param str fragmentShader: The source of the fragment shader.
- :param str attrib0:
- Attribute's name to bind to position 0 (default: 'position').
- On certain platform, this attribute MUST be active and with an
- array attached to it in order for the rendering to occur....
- """
-
- def __init__(self, vertexShader, fragmentShader,
- attrib0='position'):
- self._vertexShader = vertexShader
- self._fragmentShader = fragmentShader
- self._attrib0 = attrib0
- self._programs = weakref.WeakKeyDictionary()
-
- @staticmethod
- def _compileGL(vertexShader, fragmentShader, attrib0):
- program = gl.glCreateProgram()
-
- gl.glBindAttribLocation(program, 0, attrib0.encode('ascii'))
-
- vertex = gl.glCreateShader(gl.GL_VERTEX_SHADER)
- gl.glShaderSource(vertex, vertexShader)
- gl.glCompileShader(vertex)
- if gl.glGetShaderiv(vertex, gl.GL_COMPILE_STATUS) != gl.GL_TRUE:
- raise RuntimeError(gl.glGetShaderInfoLog(vertex))
- gl.glAttachShader(program, vertex)
- gl.glDeleteShader(vertex)
-
- fragment = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
- gl.glShaderSource(fragment, fragmentShader)
- gl.glCompileShader(fragment)
- if gl.glGetShaderiv(fragment,
- gl.GL_COMPILE_STATUS) != gl.GL_TRUE:
- raise RuntimeError(gl.glGetShaderInfoLog(fragment))
- gl.glAttachShader(program, fragment)
- gl.glDeleteShader(fragment)
-
- gl.glLinkProgram(program)
- if gl.glGetProgramiv(program, gl.GL_LINK_STATUS) != gl.GL_TRUE:
- raise RuntimeError(gl.glGetProgramInfoLog(program))
-
- attributes = {}
- for index in range(gl.glGetProgramiv(program,
- gl.GL_ACTIVE_ATTRIBUTES)):
- name = gl.glGetActiveAttrib(program, index)[0]
- namestr = name.decode('ascii')
- attributes[namestr] = gl.glGetAttribLocation(program, name)
-
- uniforms = {}
- for index in range(gl.glGetProgramiv(program, gl.GL_ACTIVE_UNIFORMS)):
- name = gl.glGetActiveUniform(program, index)[0]
- namestr = name.decode('ascii')
- uniforms[namestr] = gl.glGetUniformLocation(program, name)
-
- return program, attributes, uniforms
-
- def _getProgramInfo(self):
- glcontext = Context.getCurrent()
- if glcontext not in self._programs:
- raise RuntimeError(
- "Program was not compiled for current OpenGL context.")
- return self._programs[glcontext]
-
- @property
- def attributes(self):
- """Vertex attributes names and locations as a dict of {str: int}.
-
- WARNING:
- Read-only usage.
- To use only with a valid OpenGL context and after :meth:`use`
- has been called for this context.
- """
- return self._getProgramInfo()[1]
-
- @property
- def uniforms(self):
- """Program uniforms names and locations as a dict of {str: int}.
-
- WARNING:
- Read-only usage.
- To use only with a valid OpenGL context and after :meth:`use`
- has been called for this context.
- """
- return self._getProgramInfo()[2]
-
- @property
- def program(self):
- """OpenGL id of the program.
-
- WARNING:
- To use only with a valid OpenGL context and after :meth:`use`
- has been called for this context.
- """
- return self._getProgramInfo()[0]
-
- # def discard(self):
- # pass # Not implemented yet
-
- def use(self):
- """Make use of the program, compiling it if necessary"""
- glcontext = Context.getCurrent()
-
- if glcontext not in self._programs:
- self._programs[glcontext] = self._compileGL(
- self._vertexShader,
- self._fragmentShader,
- self._attrib0)
-
- if _logger.getEffectiveLevel() <= logging.DEBUG:
- gl.glValidateProgram(self.program)
- if gl.glGetProgramiv(
- self.program, gl.GL_VALIDATE_STATUS) != gl.GL_TRUE:
- _logger.debug('Cannot validate program: %s',
- gl.glGetProgramInfoLog(self.program))
-
- gl.glUseProgram(self.program)
-
- def setUniformMatrix(self, name, value, transpose=True, safe=False):
- """Wrap glUniformMatrix[2|3|4]fv
-
- :param str name: The name of the uniform.
- :param value: The 2D matrix (or the array of matrices, 3D).
- Matrices are 2x2, 3x3 or 4x4.
- :type value: numpy.ndarray with 2 or 3 dimensions of float32
- :param bool transpose: Whether to transpose (True, default) or not.
- :param bool safe: False: raise an error if no uniform with this name;
- True: silently ignores it.
-
- :raises KeyError: if no uniform corresponds to name.
- """
- assert value.dtype == numpy.float32
-
- shape = value.shape
- assert len(shape) in (2, 3)
- assert shape[-1] in (2, 3, 4)
- assert shape[-1] == shape[-2] # As in OpenGL|ES 2.0
-
- location = self.uniforms.get(name)
- if location is not None:
- count = 1 if len(shape) == 2 else shape[0]
- transpose = gl.GL_TRUE if transpose else gl.GL_FALSE
-
- if shape[-1] == 2:
- gl.glUniformMatrix2fv(location, count, transpose, value)
- elif shape[-1] == 3:
- gl.glUniformMatrix3fv(location, count, transpose, value)
- elif shape[-1] == 4:
- gl.glUniformMatrix4fv(location, count, transpose, value)
-
- elif not safe:
- raise KeyError('No uniform: %s' % name)
diff --git a/silx/gui/_glutils/Texture.py b/silx/gui/_glutils/Texture.py
deleted file mode 100644
index c72135a..0000000
--- a/silx/gui/_glutils/Texture.py
+++ /dev/null
@@ -1,352 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2014-2020 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""This module provides a class wrapping OpenGL 2D and 3D texture."""
-
-__authors__ = ["T. Vincent"]
-__license__ = "MIT"
-__date__ = "04/10/2016"
-
-
-try:
- from collections import abc
-except ImportError: # Python2 support
- import collections as abc
-
-from ctypes import c_void_p
-import logging
-
-import numpy
-
-from . import gl, utils
-
-
-_logger = logging.getLogger(__name__)
-
-
-class Texture(object):
- """Base class to wrap OpenGL 2D and 3D texture
-
- :param internalFormat: OpenGL texture internal format
- :param data: The data to copy to the texture or None for an empty texture
- :type data: numpy.ndarray or None
- :param format_: Input data format if different from internalFormat
- :param shape: If data is None, shape of the texture
- (height, width) or (depth, height, width)
- :type shape: List[int]
- :param int texUnit: The texture unit to use
- :param minFilter: OpenGL texture minimization filter (default: GL_NEAREST)
- :param magFilter: OpenGL texture magnification filter (default: GL_LINEAR)
- :param wrap: Texture wrap mode for dimensions: (t, s) or (r, t, s)
- If a single value is provided, it used for all dimensions.
- :type wrap: OpenGL wrap mode or 2 or 3-tuple of wrap mode
- """
-
- def __init__(self, internalFormat, data=None, format_=None,
- shape=None, texUnit=0,
- minFilter=None, magFilter=None, wrap=None):
-
- self._internalFormat = internalFormat
- if format_ is None:
- format_ = self.internalFormat
-
- if data is None:
- assert shape is not None
- else:
- assert shape is None
- data = numpy.array(data, copy=False, order='C')
- if format_ != gl.GL_RED:
- shape = data.shape[:-1] # Last dimension is channels
- else:
- shape = data.shape
-
- self._deferredUpdates = [(format_, data, None)]
-
- assert len(shape) in (2, 3)
- self._shape = tuple(shape)
- self._ndim = len(shape)
-
- self.texUnit = texUnit
-
- self._texParameterUpdates = {} # Store texture params to update
-
- self._minFilter = minFilter if minFilter is not None else gl.GL_NEAREST
- self._texParameterUpdates[gl.GL_TEXTURE_MIN_FILTER] = self._minFilter
-
- self._magFilter = magFilter if magFilter is not None else gl.GL_LINEAR
- self._texParameterUpdates[gl.GL_TEXTURE_MAG_FILTER] = self._magFilter
-
- self._name = None # Store texture ID
-
- if wrap is not None:
- if not isinstance(wrap, abc.Iterable):
- wrap = [wrap] * self.ndim
-
- assert len(wrap) == self.ndim
-
- self._texParameterUpdates[gl.GL_TEXTURE_WRAP_S] = wrap[-1]
- self._texParameterUpdates[gl.GL_TEXTURE_WRAP_T] = wrap[-2]
- if self.ndim == 3:
- self._texParameterUpdates[gl.GL_TEXTURE_WRAP_R] = wrap[0]
-
- @property
- def target(self):
- """OpenGL target type of this texture"""
- return gl.GL_TEXTURE_2D if self.ndim == 2 else gl.GL_TEXTURE_3D
-
- @property
- def ndim(self):
- """The number of dimensions: 2 or 3"""
- return self._ndim
-
- @property
- def internalFormat(self):
- """Texture internal format"""
- return self._internalFormat
-
- @property
- def shape(self):
- """Shape of the texture: (height, width) or (depth, height, width)"""
- return self._shape
-
- @property
- def name(self):
- """OpenGL texture name.
-
- It is None if not initialized or already discarded.
- """
- return self._name
-
- @property
- def minFilter(self):
- """Minifying function parameter (GL_TEXTURE_MIN_FILTER)"""
- return self._minFilter
-
- @minFilter.setter
- def minFilter(self, minFilter):
- if minFilter != self.minFilter:
- self._minFilter = minFilter
- self._texParameterUpdates[gl.GL_TEXTURE_MIN_FILTER] = minFilter
-
- @property
- def magFilter(self):
- """Magnification function parameter (GL_TEXTURE_MAG_FILTER)"""
- return self._magFilter
-
- @magFilter.setter
- def magFilter(self, magFilter):
- if magFilter != self.magFilter:
- self._magFilter = magFilter
- self._texParameterUpdates[gl.GL_TEXTURE_MAG_FILTER] = magFilter
-
- def _isPrepareRequired(self) -> bool:
- """Returns True if OpenGL texture needs to be updated.
-
- :rtype: bool
- """
- return (self._name is None or
- self._texParameterUpdates or
- self._deferredUpdates)
-
- def _prepareAndBind(self, texUnit=None):
- """Synchronizes the OpenGL texture"""
- if self._name is None:
- self._name = gl.glGenTextures(1)
-
- self._bind(texUnit)
-
- # Synchronizes texture parameters
- for pname, param in self._texParameterUpdates.items():
- gl.glTexParameter(self.target, pname, param)
- self._texParameterUpdates = {}
-
- # Copy data to texture
- for format_, data, offset in self._deferredUpdates:
- gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1)
-
- # This are the defaults, useless to set if not modified
- # gl.glPixelStorei(gl.GL_UNPACK_ROW_LENGTH, 0)
- # gl.glPixelStorei(gl.GL_UNPACK_SKIP_PIXELS, 0)
- # gl.glPixelStorei(gl.GL_UNPACK_SKIP_ROWS, 0)
- # gl.glPixelStorei(gl.GL_UNPACK_IMAGE_HEIGHT, 0)
- # gl.glPixelStorei(gl.GL_UNPACK_SKIP_IMAGES, 0)
-
- if data is None:
- data = c_void_p(0)
- type_ = gl.GL_UNSIGNED_BYTE
- else:
- type_ = utils.numpyToGLType(data.dtype)
-
- if offset is None: # Initialize texture
- if self.ndim == 2:
- _logger.debug(
- 'Creating 2D texture shape: (%d, %d),'
- ' internal format: %s, format: %s, type: %s',
- self.shape[0], self.shape[1],
- str(self.internalFormat), str(format_), str(type_))
-
- gl.glTexImage2D(
- gl.GL_TEXTURE_2D,
- 0,
- self.internalFormat,
- self.shape[1],
- self.shape[0],
- 0,
- format_,
- type_,
- data)
-
- else:
- _logger.debug(
- 'Creating 3D texture shape: (%d, %d, %d),'
- ' internal format: %s, format: %s, type: %s',
- self.shape[0], self.shape[1], self.shape[2],
- str(self.internalFormat), str(format_), str(type_))
-
- gl.glTexImage3D(
- gl.GL_TEXTURE_3D,
- 0,
- self.internalFormat,
- self.shape[2],
- self.shape[1],
- self.shape[0],
- 0,
- format_,
- type_,
- data)
-
- else: # Update already existing texture
- if self.ndim == 2:
- gl.glTexSubImage2D(gl.GL_TEXTURE_2D,
- 0,
- offset[1],
- offset[0],
- data.shape[1],
- data.shape[0],
- format_,
- type_,
- data)
-
- else:
- gl.glTexSubImage3D(gl.GL_TEXTURE_3D,
- 0,
- offset[2],
- offset[1],
- offset[0],
- data.shape[2],
- data.shape[1],
- data.shape[0],
- format_,
- type_,
- data)
-
- self._deferredUpdates = []
-
- def _bind(self, texUnit=None):
- """Bind the texture to a texture unit.
-
- :param int texUnit: The texture unit to use
- """
- if texUnit is None:
- texUnit = self.texUnit
- gl.glActiveTexture(gl.GL_TEXTURE0 + texUnit)
- gl.glBindTexture(self.target, self.name)
-
- def _unbind(self, texUnit=None):
- """Reset texture binding to a texture unit.
-
- :param int texUnit: The texture unit to use
- """
- if texUnit is None:
- texUnit = self.texUnit
- gl.glActiveTexture(gl.GL_TEXTURE0 + texUnit)
- gl.glBindTexture(self.target, 0)
-
- def prepare(self):
- """Synchronizes the OpenGL texture.
-
- This method must be called with a current OpenGL context.
- """
- if self._isPrepareRequired():
- self._prepareAndBind()
- self._unbind()
-
- def bind(self, texUnit=None):
- """Bind the texture to a texture unit.
-
- The OpenGL texture is updated if needed.
-
- This method must be called with a current OpenGL context.
-
- :param int texUnit: The texture unit to use
- """
- if self._isPrepareRequired():
- self._prepareAndBind(texUnit)
- else:
- self._bind(texUnit)
-
- def discard(self):
- """Delete associated OpenGL texture.
-
- This method must be called with a current OpenGL context.
- """
- if self._name is not None:
- gl.glDeleteTextures(self._name)
- self._name = None
- else:
- _logger.warning("Texture not initialized or already discarded")
-
- # with statement
-
- def __enter__(self):
- self.bind()
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- self._unbind()
-
- def update(self, format_, data, offset=(0, 0, 0), copy=True):
- """Update the content of the texture.
-
- Texture is not resized, so data must fit into texture with the
- given offset.
-
- This update is performed lazily during next call to
- :meth:`prepare` or :meth:`bind`.
- Data MUST not be changed until then.
-
- :param format_: The OpenGL format of the data
- :param data: The data to use to update the texture
- :param List[int] offset: Offset in the texture where to copy the data
- :param bool copy:
- True (default) to copy data, False to use as is (do not modify)
- """
- data = numpy.array(data, copy=copy, order='C')
- offset = tuple(offset)
-
- assert data.ndim == self.ndim
- assert len(offset) >= self.ndim
- for i in range(self.ndim):
- assert offset[i] + data.shape[i] <= self.shape[i]
-
- self._deferredUpdates.append((format_, data, offset))
diff --git a/silx/gui/_glutils/VertexBuffer.py b/silx/gui/_glutils/VertexBuffer.py
deleted file mode 100644
index b74b748..0000000
--- a/silx/gui/_glutils/VertexBuffer.py
+++ /dev/null
@@ -1,266 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2014-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 provides a class managing an OpenGL vertex buffer."""
-
-__authors__ = ["T. Vincent"]
-__license__ = "MIT"
-__date__ = "10/01/2017"
-
-
-import logging
-from ctypes import c_void_p
-import numpy
-
-from . import gl
-from .utils import numpyToGLType, sizeofGLType
-
-
-_logger = logging.getLogger(__name__)
-
-
-class VertexBuffer(object):
- """Object handling an OpenGL vertex buffer object
-
- :param data: Data used to fill the vertex buffer
- :type data: numpy.ndarray or None
- :param int size: Size in bytes of the buffer or None for data size
- :param usage: OpenGL vertex buffer expected usage pattern:
- GL_STREAM_DRAW, GL_STATIC_DRAW (default) or GL_DYNAMIC_DRAW
- :param target: Target buffer:
- GL_ARRAY_BUFFER (default) or GL_ELEMENT_ARRAY_BUFFER
- """
- # OpenGL|ES 2.0 subset:
- _USAGES = gl.GL_STREAM_DRAW, gl.GL_STATIC_DRAW, gl.GL_DYNAMIC_DRAW
- _TARGETS = gl.GL_ARRAY_BUFFER, gl.GL_ELEMENT_ARRAY_BUFFER
-
- def __init__(self,
- data=None,
- size=None,
- usage=None,
- target=None):
- if usage is None:
- usage = gl.GL_STATIC_DRAW
- assert usage in self._USAGES
-
- if target is None:
- target = gl.GL_ARRAY_BUFFER
- assert target in self._TARGETS
-
- self._target = target
- self._usage = usage
-
- self._name = gl.glGenBuffers(1)
- self.bind()
-
- if data is None:
- assert size is not None
- self._size = size
- gl.glBufferData(self._target,
- self._size,
- c_void_p(0),
- self._usage)
- else:
- data = numpy.array(data, copy=False, order='C')
- if size is not None:
- assert size <= data.nbytes
-
- self._size = size or data.nbytes
- gl.glBufferData(self._target,
- self._size,
- data,
- self._usage)
-
- gl.glBindBuffer(self._target, 0)
-
- @property
- def target(self):
- """The target buffer of the vertex buffer"""
- return self._target
-
- @property
- def usage(self):
- """The expected usage of the vertex buffer"""
- return self._usage
-
- @property
- def name(self):
- """OpenGL Vertex Buffer object name (int)"""
- if self._name is not None:
- return self._name
- else:
- raise RuntimeError("No OpenGL buffer resource, \
- discard has already been called")
-
- @property
- def size(self):
- """Size in bytes of the Vertex Buffer Object (int)"""
- if self._size is not None:
- return self._size
- else:
- raise RuntimeError("No OpenGL buffer resource, \
- discard has already been called")
-
- def bind(self):
- """Bind the vertex buffer"""
- gl.glBindBuffer(self._target, self.name)
-
- def update(self, data, offset=0, size=None):
- """Update vertex buffer content.
-
- :param numpy.ndarray data: The data to put in the vertex buffer
- :param int offset: Offset in bytes in the buffer where to put the data
- :param int size: If provided, size of data to copy
- """
- data = numpy.array(data, copy=False, order='C')
- if size is None:
- size = data.nbytes
- assert offset + size <= self.size
- with self:
- gl.glBufferSubData(self._target, offset, size, data)
-
- def discard(self):
- """Delete the vertex buffer"""
- if self._name is not None:
- gl.glDeleteBuffers(self._name)
- self._name = None
- self._size = None
- else:
- _logger.warning("Discard has already been called")
-
- # with statement
-
- def __enter__(self):
- self.bind()
-
- def __exit__(self, exctype, excvalue, traceback):
- gl.glBindBuffer(self._target, 0)
-
-
-class VertexBufferAttrib(object):
- """Describes data stored in a vertex buffer
-
- Convenient class to store info for glVertexAttribPointer calls
-
- :param VertexBuffer vbo: The vertex buffer storing the data
- :param int type_: The OpenGL type of the data
- :param int size: The number of data elements stored in the VBO
- :param int dimension: The number of `type_` element(s) in [1, 4]
- :param int offset: Start offset of data in the vertex buffer
- :param int stride: Data stride in the vertex buffer
- """
-
- _GL_TYPES = gl.GL_UNSIGNED_BYTE, gl.GL_FLOAT, gl.GL_INT
-
- def __init__(self,
- vbo,
- type_,
- size,
- dimension=1,
- offset=0,
- stride=0,
- normalization=False):
- self.vbo = vbo
- assert type_ in self._GL_TYPES
- self.type_ = type_
- self.size = size
- assert 1 <= dimension <= 4
- self.dimension = dimension
- self.offset = offset
- self.stride = stride
- self.normalization = bool(normalization)
-
- @property
- def itemsize(self):
- """Size in bytes of a vertex buffer element (int)"""
- return self.dimension * sizeofGLType(self.type_)
-
- itemSize = itemsize # Backward compatibility
-
- def setVertexAttrib(self, attribute):
- """Call glVertexAttribPointer with objects information"""
- normalization = gl.GL_TRUE if self.normalization else gl.GL_FALSE
- with self.vbo:
- gl.glVertexAttribPointer(attribute,
- self.dimension,
- self.type_,
- normalization,
- self.stride,
- c_void_p(self.offset))
-
- def copy(self):
- return VertexBufferAttrib(self.vbo,
- self.type_,
- self.size,
- self.dimension,
- self.offset,
- self.stride,
- self.normalization)
-
-
-def vertexBuffer(arrays, prefix=None, suffix=None, usage=None):
- """Create a single vertex buffer from multiple 1D or 2D numpy arrays.
-
- It is possible to reserve memory before and after each array in the VBO
-
- :param arrays: Arrays of data to store
- :type arrays: Iterable of numpy.ndarray
- :param prefix: If given, number of elements to reserve before each array
- :type prefix: Iterable of int or None
- :param suffix: If given, number of elements to reserve after each array
- :type suffix: Iterable of int or None
- :param int usage: vertex buffer expected usage or None for default
- :returns: List of VertexBufferAttrib objects sharing the same vertex buffer
- """
- info = []
- vbosize = 0
-
- if prefix is None:
- prefix = (0,) * len(arrays)
- if suffix is None:
- suffix = (0,) * len(arrays)
-
- for data, pre, post in zip(arrays, prefix, suffix):
- data = numpy.array(data, copy=False, order='C')
- shape = data.shape
- assert len(shape) <= 2
- type_ = numpyToGLType(data.dtype)
- size = shape[0] + pre + post
- dimension = 1 if len(shape) == 1 else shape[1]
- sizeinbytes = size * dimension * sizeofGLType(type_)
- sizeinbytes = 4 * ((sizeinbytes + 3) >> 2) # 4 bytes alignment
- copyoffset = vbosize + pre * dimension * sizeofGLType(type_)
- info.append((data, type_, size, dimension,
- vbosize, sizeinbytes, copyoffset))
- vbosize += sizeinbytes
-
- vbo = VertexBuffer(size=vbosize, usage=usage)
-
- result = []
- for data, type_, size, dimension, offset, sizeinbytes, copyoffset in info:
- copysize = data.shape[0] * dimension * sizeofGLType(type_)
- vbo.update(data, offset=copyoffset, size=copysize)
- result.append(
- VertexBufferAttrib(vbo, type_, size, dimension, offset, 0))
- return result
diff --git a/silx/gui/_glutils/__init__.py b/silx/gui/_glutils/__init__.py
deleted file mode 100644
index e88affd..0000000
--- a/silx/gui/_glutils/__init__.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2014-2019 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# 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 utility functions to handle OpenGL resources.
-
-The :mod:`gl` module provides a wrapper to OpenGL based on PyOpenGL.
-"""
-
-__authors__ = ["T. Vincent"]
-__license__ = "MIT"
-__date__ = "25/07/2016"
-
-
-# OpenGL convenient functions
-from .OpenGLWidget import OpenGLWidget # noqa
-from . import Context # noqa
-from .FramebufferTexture import FramebufferTexture # noqa
-from .Program import Program # noqa
-from .Texture import Texture # noqa
-from .VertexBuffer import VertexBuffer, VertexBufferAttrib, vertexBuffer # noqa
-from .utils import sizeofGLType, isSupportedGLType, numpyToGLType # noqa
-from .utils import segmentTrianglesIntersection # noqa
diff --git a/silx/gui/_glutils/font.py b/silx/gui/_glutils/font.py
deleted file mode 100644
index 6a4c489..0000000
--- a/silx/gui/_glutils/font.py
+++ /dev/null
@@ -1,163 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016-2020 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.
-#
-# ###########################################################################*/
-"""Text rasterisation feature leveraging Qt font and text layout support."""
-
-__authors__ = ["T. Vincent"]
-__license__ = "MIT"
-__date__ = "13/10/2016"
-
-
-import logging
-import numpy
-
-from ..utils.image import convertQImageToArray
-from .. import qt
-
-_logger = logging.getLogger(__name__)
-
-
-def getDefaultFontFamily():
- """Returns the default font family of the application"""
- return qt.QApplication.instance().font().family()
-
-
-# Font weights
-ULTRA_LIGHT = 0
-"""Lightest characters: Minimum font weight"""
-
-LIGHT = 25
-"""Light characters"""
-
-NORMAL = 50
-"""Normal characters"""
-
-SEMI_BOLD = 63
-"""Between normal and bold characters"""
-
-BOLD = 74
-"""Thicker characters"""
-
-BLACK = 87
-"""Really thick characters"""
-
-ULTRA_BLACK = 99
-"""Thickest characters: Maximum font weight"""
-
-
-def rasterText(text, font,
- size=-1,
- weight=-1,
- italic=False,
- devicePixelRatio=1.0):
- """Raster text using Qt.
-
- It supports multiple lines.
-
- :param str text: The text to raster
- :param font: Font name or QFont to use
- :type font: str or :class:`QFont`
- :param int size:
- Font size in points
- Used only if font is given as name.
- :param int weight:
- Font weight in [0, 99], see QFont.Weight.
- Used only if font is given as name.
- :param bool italic:
- True for italic font (default: False).
- Used only if font is given as name.
- :param float devicePixelRatio:
- The current ratio between device and device-independent pixel
- (default: 1.0)
- :return: Corresponding image in gray scale and baseline offset from top
- :rtype: (HxW numpy.ndarray of uint8, int)
- """
- if not text:
- _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)
-
- # 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(int(width),
- int(bounds.height() * devicePixelRatio + 2),
- qt.QImage.Format_RGB888)
- if (devicePixelRatio != 1.0 and
- hasattr(image, 'setDevicePixelRatio')): # Qt 5
- image.setDevicePixelRatio(devicePixelRatio)
-
- # TODO if Qt5 use Format_Grayscale8 instead
- image.fill(0)
-
- # Raster text
- painter = qt.QPainter()
- painter.begin(image)
- painter.setPen(qt.Qt.white)
- painter.setFont(font)
- painter.drawText(bounds, qt.Qt.TextExpandTabs, text)
- painter.end()
-
- array = convertQImageToArray(image)
-
- # RGB to R
- array = numpy.ascontiguousarray(array[:, :, 0])
-
- # Remove leading and trailing empty columns but one on each side
- column_cumsum = numpy.cumsum(numpy.sum(array, axis=0))
- array = array[:, column_cumsum.argmin():column_cumsum.argmax() + 2]
-
- # Remove leading and trailing empty rows but one on each side
- row_cumsum = numpy.cumsum(numpy.sum(array, axis=1))
- min_row = row_cumsum.argmin()
- array = array[min_row:row_cumsum.argmax() + 2, :]
-
- return array, metrics.ascent() - min_row
diff --git a/silx/gui/_glutils/gl.py b/silx/gui/_glutils/gl.py
deleted file mode 100644
index 608d9ce..0000000
--- a/silx/gui/_glutils/gl.py
+++ /dev/null
@@ -1,168 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2014-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 loads PyOpenGL and provides a namespace for OpenGL."""
-
-__authors__ = ["T. Vincent"]
-__license__ = "MIT"
-__date__ = "25/07/2016"
-
-
-from contextlib import contextmanager as _contextmanager
-from ctypes import c_uint
-import logging
-
-_logger = logging.getLogger(__name__)
-
-import OpenGL
-# Set the following to true for debugging
-if _logger.getEffectiveLevel() <= logging.DEBUG:
- _logger.debug('Enabling PyOpenGL debug flags')
- OpenGL.ERROR_LOGGING = True
- OpenGL.ERROR_CHECKING = True
- OpenGL.ERROR_ON_COPY = True
-else:
- OpenGL.ERROR_LOGGING = False
- OpenGL.ERROR_CHECKING = False
- OpenGL.ERROR_ON_COPY = False
-
-import OpenGL.GL as _GL
-from OpenGL.GL import * # noqa
-
-# Extentions core in OpenGL 3
-from OpenGL.GL.ARB import framebuffer_object as _FBO
-from OpenGL.GL.ARB.framebuffer_object import * # noqa
-from OpenGL.GL.ARB.texture_rg import GL_R32F, GL_R16F # noqa
-from OpenGL.GL.ARB.texture_rg import GL_R16, GL_R8 # noqa
-
-# PyOpenGL 3.0.1 does not define it
-try:
- GLchar
-except NameError:
- from ctypes import c_char
- GLchar = c_char
-
-
-def testGL():
- """Test if required OpenGL version and extensions are available.
-
- This MUST be run with an active OpenGL context.
- """
- version = glGetString(GL_VERSION).split()[0] # get version number
- major, minor = int(version[0]), int(version[2])
- if major < 2 or (major == 2 and minor < 1):
- raise RuntimeError(
- "Requires at least OpenGL version 2.1, running with %s" % version)
-
- from OpenGL.GL.ARB.framebuffer_object import glInitFramebufferObjectARB
- from OpenGL.GL.ARB.texture_rg import glInitTextureRgARB
-
- if not glInitFramebufferObjectARB():
- raise RuntimeError(
- "OpenGL GL_ARB_framebuffer_object extension required !")
-
- if not glInitTextureRgARB():
- raise RuntimeError("OpenGL GL_ARB_texture_rg extension required !")
-
-
-# Additional setup
-if hasattr(glget, 'addGLGetConstant'):
- glget.addGLGetConstant(GL_FRAMEBUFFER_BINDING, (1,))
-
-
-@_contextmanager
-def enabled(capacity, enable=True):
- """Context manager enabling an OpenGL capacity.
-
- This is not checking the current state of the capacity.
-
- :param capacity: The OpenGL capacity enum to enable/disable
- :param bool enable:
- True (default) to enable during context, False to disable
- """
- if bool(enable) == glGetBoolean(capacity):
- # Already in the right state: noop
- yield
- elif enable:
- glEnable(capacity)
- yield
- glDisable(capacity)
- else:
- glDisable(capacity)
- yield
- glEnable(capacity)
-
-
-def disabled(capacity, disable=True):
- """Context manager disabling an OpenGL capacity.
-
- This is not checking the current state of the capacity.
-
- :param capacity: The OpenGL capacity enum to disable/enable
- :param bool disable:
- True (default) to disable during context, False to enable
- """
- return enabled(capacity, not disable)
-
-
-# Additional OpenGL wrapping
-
-def glGetActiveAttrib(program, index):
- """Wrap PyOpenGL glGetActiveAttrib"""
- bufsize = glGetProgramiv(program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH)
- length = GLsizei()
- size = GLint()
- type_ = GLenum()
- name = (GLchar * bufsize)()
-
- _GL.glGetActiveAttrib(program, index, bufsize, length, size, type_, name)
- return name.value, size.value, type_.value
-
-
-def glDeleteRenderbuffers(buffers):
- if not hasattr(buffers, '__len__'): # Support single int argument
- buffers = [buffers]
- length = len(buffers)
- _FBO.glDeleteRenderbuffers(length, (c_uint * length)(*buffers))
-
-
-def glDeleteFramebuffers(buffers):
- if not hasattr(buffers, '__len__'): # Support single int argument
- buffers = [buffers]
- length = len(buffers)
- _FBO.glDeleteFramebuffers(length, (c_uint * length)(*buffers))
-
-
-def glDeleteBuffers(buffers):
- if not hasattr(buffers, '__len__'): # Support single int argument
- buffers = [buffers]
- length = len(buffers)
- _GL.glDeleteBuffers(length, (c_uint * length)(*buffers))
-
-
-def glDeleteTextures(textures):
- if not hasattr(textures, '__len__'): # Support single int argument
- textures = [textures]
- length = len(textures)
- _GL.glDeleteTextures((c_uint * length)(*textures))
diff --git a/silx/gui/_glutils/utils.py b/silx/gui/_glutils/utils.py
deleted file mode 100644
index d5627ef..0000000
--- a/silx/gui/_glutils/utils.py
+++ /dev/null
@@ -1,121 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2014-2019 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""This module provides conversion functions between OpenGL and numpy types.
-"""
-
-__authors__ = ["T. Vincent"]
-__license__ = "MIT"
-__date__ = "10/01/2017"
-
-import numpy
-
-from OpenGL.constants import BYTE_SIZES as _BYTE_SIZES
-from OpenGL.constants import ARRAY_TO_GL_TYPE_MAPPING as _ARRAY_TO_GL_TYPE_MAPPING
-
-
-def sizeofGLType(type_):
- """Returns the size in bytes of an element of type `type_`"""
- return _BYTE_SIZES[type_]
-
-
-def isSupportedGLType(type_):
- """Test if a numpy type or dtype can be converted to a GL type."""
- return numpy.dtype(type_).char in _ARRAY_TO_GL_TYPE_MAPPING
-
-
-def numpyToGLType(type_):
- """Returns the GL type corresponding the provided numpy type or dtype."""
- return _ARRAY_TO_GL_TYPE_MAPPING[numpy.dtype(type_).char]
-
-
-def segmentTrianglesIntersection(segment, triangles):
- """Check for segment/triangles intersection.
-
- This is based on signed tetrahedron volume comparison.
-
- See A. Kensler, A., Shirley, P.
- Optimizing Ray-Triangle Intersection via Automated Search.
- Symposium on Interactive Ray Tracing, vol. 0, p33-38 (2006)
-
- :param numpy.ndarray segment:
- Segment end points as a 2x3 array of coordinates
- :param numpy.ndarray triangles:
- Nx3x3 array of triangles
- :return: (triangle indices, segment parameter, barycentric coord)
- Indices of intersected triangles, "depth" along the segment
- of the intersection point and barycentric coordinates of intersection
- point in the triangle.
- :rtype: List[numpy.ndarray]
- """
- # TODO triangles from vertices + indices
- # TODO early rejection? e.g., check segment bbox vs triangle bbox
- segment = numpy.asarray(segment)
- assert segment.ndim == 2
- assert segment.shape == (2, 3)
-
- triangles = numpy.asarray(triangles)
- assert triangles.ndim == 3
- assert triangles.shape[1] == 3
-
- # Test line/triangles intersection
- d = segment[1] - segment[0]
- t0s0 = segment[0] - triangles[:, 0, :]
- edge01 = triangles[:, 1, :] - triangles[:, 0, :]
- edge02 = triangles[:, 2, :] - triangles[:, 0, :]
-
- dCrossEdge02 = numpy.cross(d, edge02)
- t0s0CrossEdge01 = numpy.cross(t0s0, edge01)
- volume = numpy.sum(dCrossEdge02 * edge01, axis=1)
- del edge01
- subVolumes = numpy.empty((len(triangles), 3), dtype=triangles.dtype)
- subVolumes[:, 1] = numpy.sum(dCrossEdge02 * t0s0, axis=1)
- del dCrossEdge02
- subVolumes[:, 2] = numpy.sum(t0s0CrossEdge01 * d, axis=1)
- subVolumes[:, 0] = volume - subVolumes[:, 1] - subVolumes[:, 2]
- intersect = numpy.logical_or(
- numpy.all(subVolumes >= 0., axis=1), # All positive
- numpy.all(subVolumes <= 0., axis=1)) # All negative
- intersect = numpy.where(intersect)[0] # Indices of intersected triangles
-
- # Get barycentric coordinates
- barycentric = subVolumes[intersect] / volume[intersect].reshape(-1, 1)
- del subVolumes
-
- # Test segment/triangles intersection
- volAlpha = numpy.sum(t0s0CrossEdge01[intersect] * edge02[intersect], axis=1)
- t = volAlpha / volume[intersect] # segment parameter of intersected triangles
- del t0s0CrossEdge01
- del edge02
- del volAlpha
- del volume
-
- inSegmentMask = numpy.logical_and(t >= 0., t <= 1.)
- intersect = intersect[inSegmentMask]
- t = t[inSegmentMask]
- barycentric = barycentric[inSegmentMask]
-
- # Sort intersecting triangles by t
- indices = numpy.argsort(t)
- return intersect[indices], t[indices], barycentric[indices]
diff --git a/silx/gui/colors.py b/silx/gui/colors.py
deleted file mode 100755
index db837b5..0000000
--- a/silx/gui/colors.py
+++ /dev/null
@@ -1,1326 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2015-2021 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""This module provides API to manage colors.
-"""
-
-from __future__ import absolute_import
-
-__authors__ = ["T. Vincent", "H.Payno"]
-__license__ = "MIT"
-__date__ = "29/01/2019"
-
-import numpy
-import logging
-import collections
-import warnings
-
-from silx.gui import qt
-from silx.gui.utils import blockSignals
-from silx.math.combo import min_max
-from silx.math import colormap as _colormap
-from silx.utils.exceptions import NotEditableError
-from silx.utils import deprecation
-from silx.resources import resource_filename as _resource_filename
-
-
-_logger = logging.getLogger(__name__)
-
-try:
- import silx.gui.utils.matplotlib # noqa Initalize matplotlib
- from matplotlib import cm as _matplotlib_cm
- from matplotlib.pyplot import colormaps as _matplotlib_colormaps
-except ImportError:
- _logger.info("matplotlib not available, only embedded colormaps available")
- _matplotlib_cm = None
- _matplotlib_colormaps = None
-
-
-_COLORDICT = {}
-"""Dictionary of common colors."""
-
-_COLORDICT['b'] = _COLORDICT['blue'] = '#0000ff'
-_COLORDICT['r'] = _COLORDICT['red'] = '#ff0000'
-_COLORDICT['g'] = _COLORDICT['green'] = '#00ff00'
-_COLORDICT['k'] = _COLORDICT['black'] = '#000000'
-_COLORDICT['w'] = _COLORDICT['white'] = '#ffffff'
-_COLORDICT['pink'] = '#ff66ff'
-_COLORDICT['brown'] = '#a52a2a'
-_COLORDICT['orange'] = '#ff9900'
-_COLORDICT['violet'] = '#6600ff'
-_COLORDICT['gray'] = _COLORDICT['grey'] = '#a0a0a4'
-# _COLORDICT['darkGray'] = _COLORDICT['darkGrey'] = '#808080'
-# _COLORDICT['lightGray'] = _COLORDICT['lightGrey'] = '#c0c0c0'
-_COLORDICT['y'] = _COLORDICT['yellow'] = '#ffff00'
-_COLORDICT['m'] = _COLORDICT['magenta'] = '#ff00ff'
-_COLORDICT['c'] = _COLORDICT['cyan'] = '#00ffff'
-_COLORDICT['darkBlue'] = '#000080'
-_COLORDICT['darkRed'] = '#800000'
-_COLORDICT['darkGreen'] = '#008000'
-_COLORDICT['darkBrown'] = '#660000'
-_COLORDICT['darkCyan'] = '#008080'
-_COLORDICT['darkYellow'] = '#808000'
-_COLORDICT['darkMagenta'] = '#800080'
-_COLORDICT['transparent'] = '#00000000'
-
-
-# FIXME: It could be nice to expose a functional API instead of that attribute
-COLORDICT = _COLORDICT
-
-
-_LUT_DESCRIPTION = collections.namedtuple("_LUT_DESCRIPTION", ["source", "cursor_color", "preferred"])
-"""Description of a LUT for internal purpose."""
-
-
-_AVAILABLE_LUTS = collections.OrderedDict([
- ('gray', _LUT_DESCRIPTION('builtin', 'pink', True)),
- ('reversed gray', _LUT_DESCRIPTION('builtin', 'pink', True)),
- ('red', _LUT_DESCRIPTION('builtin', 'green', True)),
- ('green', _LUT_DESCRIPTION('builtin', 'pink', True)),
- ('blue', _LUT_DESCRIPTION('builtin', 'yellow', True)),
- ('viridis', _LUT_DESCRIPTION('resource', 'pink', True)),
- ('cividis', _LUT_DESCRIPTION('resource', 'pink', True)),
- ('magma', _LUT_DESCRIPTION('resource', 'green', True)),
- ('inferno', _LUT_DESCRIPTION('resource', 'green', True)),
- ('plasma', _LUT_DESCRIPTION('resource', 'green', True)),
- ('temperature', _LUT_DESCRIPTION('builtin', 'pink', True)),
- ('jet', _LUT_DESCRIPTION('matplotlib', 'pink', True)),
- ('hsv', _LUT_DESCRIPTION('matplotlib', 'black', True)),
-])
-"""Description for internal porpose of all the default LUT provided by the library."""
-
-
-DEFAULT_MIN_LIN = 0
-"""Default min value if in linear normalization"""
-DEFAULT_MAX_LIN = 1
-"""Default max value if in linear normalization"""
-
-
-def rgba(color, colorDict=None):
- """Convert color code '#RRGGBB' and '#RRGGBBAA' to a tuple (R, G, B, A)
- of floats.
-
- It also supports RGB(A) from uint8 in [0, 255], float in [0, 1], and
- QColor as color argument.
-
- :param str color: The color to convert
- :param dict colorDict: A dictionary of color name conversion to color code
- :returns: RGBA colors as floats in [0., 1.]
- :rtype: tuple
- """
- if colorDict is None:
- colorDict = _COLORDICT
-
- if hasattr(color, 'getRgbF'): # QColor support
- color = color.getRgbF()
-
- values = numpy.asarray(color).ravel()
-
- if values.dtype.kind in 'iuf': # integer or float
- # Color is an array
- assert len(values) in (3, 4)
-
- # Convert from integers in [0, 255] to float in [0, 1]
- if values.dtype.kind in 'iu':
- values = values / 255.
-
- # Clip to [0, 1]
- values[values < 0.] = 0.
- values[values > 1.] = 1.
-
- if len(values) == 3:
- return values[0], values[1], values[2], 1.
- else:
- return tuple(values)
-
- # We assume color is a string
- if not color.startswith('#'):
- color = colorDict[color]
-
- assert len(color) in (7, 9) and color[0] == '#'
- r = int(color[1:3], 16) / 255.
- g = int(color[3:5], 16) / 255.
- b = int(color[5:7], 16) / 255.
- a = int(color[7:9], 16) / 255. if len(color) == 9 else 1.
- return r, g, b, a
-
-
-def greyed(color, colorDict=None):
- """Convert color code '#RRGGBB' and '#RRGGBBAA' to a grey color
- (R, G, B, A).
-
- It also supports RGB(A) from uint8 in [0, 255], float in [0, 1], and
- QColor as color argument.
-
- :param str color: The color to convert
- :param dict colorDict: A dictionary of color name conversion to color code
- :returns: RGBA colors as floats in [0., 1.]
- :rtype: tuple
- """
- r, g, b, a = rgba(color=color, colorDict=colorDict)
- g = 0.21 * r + 0.72 * g + 0.07 * b
- return g, g, g, a
-
-
-def asQColor(color):
- """Convert color code '#RRGGBB' and '#RRGGBBAA' to a `qt.QColor`.
-
- It also supports RGB(A) from uint8 in [0, 255], float in [0, 1], and
- QColor as color argument.
-
- :param str color: The color to convert
- :rtype: qt.QColor
- """
- color = rgba(color)
- return qt.QColor.fromRgbF(*color)
-
-
-def cursorColorForColormap(colormapName):
- """Get a color suitable for overlay over a colormap.
-
- :param str colormapName: The name of the colormap.
- :return: Name of the color.
- :rtype: str
- """
- description = _AVAILABLE_LUTS.get(colormapName, None)
- if description is not None:
- color = description.cursor_color
- if color is not None:
- return color
- return 'black'
-
-
-# Colormap loader
-
-_COLORMAP_CACHE = {}
-"""Cache already used colormaps as name: color LUT"""
-
-
-def _arrayToRgba8888(colors):
- """Convert colors from a numpy array using float (0..1) int or uint
- (0..255) to uint8 RGBA.
-
- :param numpy.ndarray colors: Array of float int or uint colors to convert
- :return: colors as uint8
- :rtype: numpy.ndarray
- """
- assert len(colors.shape) == 2
- assert colors.shape[1] in (3, 4)
-
- if colors.dtype == numpy.uint8:
- pass
- elif colors.dtype.kind == 'f':
- # Each bin is [N, N+1[ except the last one: [255, 256]
- colors = numpy.clip(colors.astype(numpy.float64) * 256, 0., 255.)
- colors = colors.astype(numpy.uint8)
- elif colors.dtype.kind in 'iu':
- colors = numpy.clip(colors, 0, 255)
- colors = colors.astype(numpy.uint8)
-
- if colors.shape[1] == 3:
- tmp = numpy.empty((len(colors), 4), dtype=numpy.uint8)
- tmp[:, 0:3] = colors
- tmp[:, 3] = 255
- colors = tmp
-
- return colors
-
-
-def _createColormapLut(name):
- """Returns the color LUT corresponding to a colormap name
-
- :param str name: Name of the colormap to load
- :returns: Corresponding table of colors
- :rtype: numpy.ndarray
- :raise ValueError: If no colormap corresponds to name
- """
- description = _AVAILABLE_LUTS.get(name)
- use_mpl = False
- if description is not None:
- if description.source == "builtin":
- # Build colormap LUT
- lut = numpy.zeros((256, 4), dtype=numpy.uint8)
- lut[:, 3] = 255
-
- if name == 'gray':
- lut[:, :3] = numpy.arange(256, dtype=numpy.uint8).reshape(-1, 1)
- elif name == 'reversed gray':
- lut[:, :3] = numpy.arange(255, -1, -1, dtype=numpy.uint8).reshape(-1, 1)
- elif name == 'red':
- lut[:, 0] = numpy.arange(256, dtype=numpy.uint8)
- elif name == 'green':
- lut[:, 1] = numpy.arange(256, dtype=numpy.uint8)
- elif name == 'blue':
- lut[:, 2] = numpy.arange(256, dtype=numpy.uint8)
- elif name == 'temperature':
- # Red
- lut[128:192, 0] = numpy.arange(2, 255, 4, dtype=numpy.uint8)
- lut[192:, 0] = 255
- # Green
- lut[:64, 1] = numpy.arange(0, 255, 4, dtype=numpy.uint8)
- lut[64:192, 1] = 255
- lut[192:, 1] = numpy.arange(252, -1, -4, dtype=numpy.uint8)
- # Blue
- lut[:64, 2] = 255
- lut[64:128, 2] = numpy.arange(254, 0, -4, dtype=numpy.uint8)
- else:
- raise RuntimeError("Built-in colormap not implemented")
- return lut
-
- elif description.source == "resource":
- # Load colormap LUT
- colors = numpy.load(_resource_filename("gui/colormaps/%s.npy" % name))
- # Convert to uint8 and add alpha channel
- lut = _arrayToRgba8888(colors)
- return lut
-
- elif description.source == "matplotlib":
- use_mpl = True
-
- else:
- raise RuntimeError("Internal LUT source '%s' unsupported" % description.source)
-
- # Here it expect a matplotlib LUTs
-
- if use_mpl:
- # matplotlib is mandatory
- if _matplotlib_cm is None:
- raise ValueError("The colormap '%s' expect matplotlib, but matplotlib is not installed" % name)
-
- if _matplotlib_cm is not None: # Try to load with matplotlib
- colormap = _matplotlib_cm.get_cmap(name)
- lut = colormap(numpy.linspace(0, 1, colormap.N, endpoint=True))
- lut = _arrayToRgba8888(lut)
- return lut
-
- raise ValueError("Unknown colormap '%s'" % name)
-
-
-def _getColormap(name):
- """Returns the color LUT corresponding to a colormap name
-
- :param str name: Name of the colormap to load
- :returns: Corresponding table of colors
- :rtype: numpy.ndarray
- :raise ValueError: If no colormap corresponds to name
- """
- name = str(name)
- if name not in _COLORMAP_CACHE:
- lut = _createColormapLut(name)
- _COLORMAP_CACHE[name] = lut
- return _COLORMAP_CACHE[name]
-
-
-# Normalizations
-
-class _NormalizationMixIn:
- """Colormap normalization mix-in class"""
-
- DEFAULT_RANGE = 0, 1
- """Fallback for (vmin, vmax)"""
-
- def isValid(self, value):
- """Check if a value is in the valid range for this normalization.
-
- Override in subclass.
-
- :param Union[float,numpy.ndarray] value:
- :rtype: Union[bool,numpy.ndarray]
- """
- if isinstance(value, collections.abc.Iterable):
- return numpy.ones_like(value, dtype=numpy.bool_)
- else:
- return True
-
- def autoscale(self, data, mode):
- """Returns range for given data and autoscale mode.
-
- :param Union[None,numpy.ndarray] data:
- :param str mode: Autoscale mode, see :class:`Colormap`
- :returns: Range as (min, max)
- :rtype: Tuple[float,float]
- """
- data = None if data is None else numpy.array(data, copy=False)
- if data is None or data.size == 0:
- return self.DEFAULT_RANGE
-
- if mode == Colormap.MINMAX:
- vmin, vmax = self.autoscaleMinMax(data)
- elif mode == Colormap.STDDEV3:
- dmin, dmax = self.autoscaleMinMax(data)
- stdmin, stdmax = self.autoscaleMean3Std(data)
- if dmin is None:
- vmin = stdmin
- elif stdmin is None:
- vmin = dmin
- else:
- vmin = max(dmin, stdmin)
-
- if dmax is None:
- vmax = stdmax
- elif stdmax is None:
- vmax = dmax
- else:
- vmax = min(dmax, stdmax)
-
- else:
- raise ValueError('Unsupported mode: %s' % mode)
-
- # Check returned range and handle fallbacks
- if vmin is None or not numpy.isfinite(vmin):
- vmin = self.DEFAULT_RANGE[0]
- if vmax is None or not numpy.isfinite(vmax):
- vmax = self.DEFAULT_RANGE[1]
- if vmax < vmin:
- vmax = vmin
- return float(vmin), float(vmax)
-
- def autoscaleMinMax(self, data):
- """Autoscale using min/max
-
- :param numpy.ndarray data:
- :returns: (vmin, vmax)
- :rtype: Tuple[float,float]
- """
- data = data[self.isValid(data)]
- if data.size == 0:
- return None, None
- result = min_max(data, min_positive=False, finite=True)
- return result.minimum, result.maximum
-
- def autoscaleMean3Std(self, data):
- """Autoscale using mean+/-3std
-
- This implementation only works for normalization that do NOT
- use the data range.
- Override this method for normalization using the range.
-
- :param numpy.ndarray data:
- :returns: (vmin, vmax)
- :rtype: Tuple[float,float]
- """
- # Use [0, 1] as data range for normalization not using range
- normdata = self.apply(data, 0., 1.)
- if normdata.dtype.kind == 'f': # Replaces inf by NaN
- normdata[numpy.isfinite(normdata) == False] = numpy.nan
- if normdata.size == 0: # Fallback
- return None, None
-
- with warnings.catch_warnings():
- warnings.simplefilter('ignore', category=RuntimeWarning)
- # Ignore nanmean "Mean of empty slice" warning and
- # nanstd "Degrees of freedom <= 0 for slice" warning
- mean, std = numpy.nanmean(normdata), numpy.nanstd(normdata)
-
- return self.revert(mean - 3 * std, 0., 1.), self.revert(mean + 3 * std, 0., 1.)
-
-
-class _LinearNormalizationMixIn(_NormalizationMixIn):
- """Colormap normalization mix-in class specific to autoscale taken from initial range"""
-
- def autoscaleMean3Std(self, data):
- """Autoscale using mean+/-3std
-
- Do the autoscale on the data itself, not the normalized data.
-
- :param numpy.ndarray data:
- :returns: (vmin, vmax)
- :rtype: Tuple[float,float]
- """
- if data.dtype.kind == 'f': # Replaces inf by NaN
- data = numpy.array(data, copy=True) # Work on a copy
- data[numpy.isfinite(data) == False] = numpy.nan
- if data.size == 0: # Fallback
- return None, None
- with warnings.catch_warnings():
- warnings.simplefilter('ignore', category=RuntimeWarning)
- # Ignore nanmean "Mean of empty slice" warning and
- # nanstd "Degrees of freedom <= 0 for slice" warning
- mean, std = numpy.nanmean(data), numpy.nanstd(data)
- return mean - 3 * std, mean + 3 * std
-
-
-class _LinearNormalization(_colormap.LinearNormalization, _LinearNormalizationMixIn):
- """Linear normalization"""
- def __init__(self):
- _colormap.LinearNormalization.__init__(self)
- _LinearNormalizationMixIn.__init__(self)
-
-
-class _LogarithmicNormalization(_colormap.LogarithmicNormalization, _NormalizationMixIn):
- """Logarithm normalization"""
-
- DEFAULT_RANGE = 1, 10
-
- def __init__(self):
- _colormap.LogarithmicNormalization.__init__(self)
- _NormalizationMixIn.__init__(self)
-
- def isValid(self, value):
- return value > 0.
-
- def autoscaleMinMax(self, data):
- result = min_max(data, min_positive=True, finite=True)
- return result.min_positive, result.maximum
-
-
-class _SqrtNormalization(_colormap.SqrtNormalization, _NormalizationMixIn):
- """Square root normalization"""
-
- DEFAULT_RANGE = 0, 1
-
- def __init__(self):
- _colormap.SqrtNormalization.__init__(self)
- _NormalizationMixIn.__init__(self)
-
- def isValid(self, value):
- return value >= 0.
-
-
-class _GammaNormalization(_colormap.PowerNormalization, _LinearNormalizationMixIn):
- """Gamma correction normalization:
-
- Linear normalization to [0, 1] followed by power normalization.
-
- :param gamma: Gamma correction factor
- """
- def __init__(self, gamma):
- _colormap.PowerNormalization.__init__(self, gamma)
- _LinearNormalizationMixIn.__init__(self)
-
-
-class _ArcsinhNormalization(_colormap.ArcsinhNormalization, _NormalizationMixIn):
- """Inverse hyperbolic sine normalization"""
-
- def __init__(self):
- _colormap.ArcsinhNormalization.__init__(self)
- _NormalizationMixIn.__init__(self)
-
-
-class Colormap(qt.QObject):
- """Description of a colormap
-
- If no `name` nor `colors` are provided, a default gray LUT is used.
-
- :param str name: Name of the colormap
- :param tuple colors: optional, custom colormap.
- Nx3 or Nx4 numpy array of RGB(A) colors,
- either uint8 or float in [0, 1].
- If 'name' is None, then this array is used as the colormap.
- :param str normalization: Normalization: 'linear' (default) or 'log'
- :param vmin: Lower bound of the colormap or None for autoscale (default)
- :type vmin: Union[None, float]
- :param vmax: Upper bounds of the colormap or None for autoscale (default)
- :type vmax: Union[None, float]
- """
-
- LINEAR = 'linear'
- """constant for linear normalization"""
-
- LOGARITHM = 'log'
- """constant for logarithmic normalization"""
-
- SQRT = 'sqrt'
- """constant for square root normalization"""
-
- GAMMA = 'gamma'
- """Constant for gamma correction normalization"""
-
- ARCSINH = 'arcsinh'
- """constant for inverse hyperbolic sine normalization"""
-
- _BASIC_NORMALIZATIONS = {
- LINEAR: _LinearNormalization(),
- LOGARITHM: _LogarithmicNormalization(),
- SQRT: _SqrtNormalization(),
- ARCSINH: _ArcsinhNormalization(),
- }
- """Normalizations without parameters"""
-
- NORMALIZATIONS = LINEAR, LOGARITHM, SQRT, GAMMA, ARCSINH
- """Tuple of managed normalizations"""
-
- MINMAX = 'minmax'
- """constant for autoscale using min/max data range"""
-
- STDDEV3 = 'stddev3'
- """constant for autoscale using mean +/- 3*std(data)
- with a clamp on min/max of the data"""
-
- AUTOSCALE_MODES = (MINMAX, STDDEV3)
- """Tuple of managed auto scale algorithms"""
-
- sigChanged = qt.Signal()
- """Signal emitted when the colormap has changed."""
-
- _DEFAULT_NAN_COLOR = 255, 255, 255, 0
-
- def __init__(self, name=None, colors=None, normalization=LINEAR, vmin=None, vmax=None, autoscaleMode=MINMAX):
- qt.QObject.__init__(self)
- self._editable = True
- self.__gamma = 2.0
- # Default NaN color: fully transparent white
- self.__nanColor = numpy.array(self._DEFAULT_NAN_COLOR, dtype=numpy.uint8)
-
- assert normalization in Colormap.NORMALIZATIONS
- assert autoscaleMode in Colormap.AUTOSCALE_MODES
-
- if normalization is Colormap.LOGARITHM:
- if (vmin is not None and vmin < 0) or (vmax is not None and vmax < 0):
- m = "Unsuported vmin (%s) and/or vmax (%s) given for a log scale."
- m += ' Autoscale will be performed.'
- m = m % (vmin, vmax)
- _logger.warning(m)
- vmin = None
- vmax = None
-
- self._name = None
- self._colors = None
-
- if colors is not None and name is not None:
- deprecation.deprecated_warning("Argument",
- name="silx.gui.plot.Colors",
- reason="name and colors can't be used at the same time",
- since_version="0.10.0",
- skip_backtrace_count=1)
-
- colors = None
-
- if name is not None:
- self.setName(name) # And resets colormap LUT
- elif colors is not None:
- self.setColormapLUT(colors)
- else:
- # Default colormap is grey
- self.setName("gray")
-
- self._normalization = str(normalization)
- self._autoscaleMode = str(autoscaleMode)
- self._vmin = float(vmin) if vmin is not None else None
- self._vmax = float(vmax) if vmax is not None else None
-
- def setFromColormap(self, other):
- """Set this colormap using information from the `other` colormap.
-
- :param ~silx.gui.colors.Colormap other: Colormap to use as reference.
- """
- if not self.isEditable():
- raise NotEditableError('Colormap is not editable')
- if self == other:
- return
- with blockSignals(self):
- name = other.getName()
- if name is not None:
- self.setName(name)
- else:
- self.setColormapLUT(other.getColormapLUT())
- self.setNaNColor(other.getNaNColor())
- self.setNormalization(other.getNormalization())
- self.setGammaNormalizationParameter(
- other.getGammaNormalizationParameter())
- self.setAutoscaleMode(other.getAutoscaleMode())
- self.setVRange(*other.getVRange())
- self.setEditable(other.isEditable())
- self.sigChanged.emit()
-
- def getNColors(self, nbColors=None):
- """Returns N colors computed by sampling the colormap regularly.
-
- :param nbColors:
- The number of colors in the returned array or None for the default value.
- The default value is the size of the colormap LUT.
- :type nbColors: int or None
- :return: 2D array of uint8 of shape (nbColors, 4)
- :rtype: numpy.ndarray
- """
- # Handle default value for nbColors
- if nbColors is None:
- return numpy.array(self._colors, copy=True)
- else:
- nbColors = int(nbColors)
- colormap = self.copy()
- colormap.setNormalization(Colormap.LINEAR)
- colormap.setVRange(vmin=0, vmax=nbColors - 1)
- colors = colormap.applyToData(
- numpy.arange(nbColors, dtype=numpy.int32))
- return colors
-
- def getName(self):
- """Return the name of the colormap
- :rtype: str
- """
- return self._name
-
- def setName(self, name):
- """Set the name of the colormap to use.
-
- :param str name: The name of the colormap.
- At least the following names are supported: 'gray',
- 'reversed gray', 'temperature', 'red', 'green', 'blue', 'jet',
- 'viridis', 'magma', 'inferno', 'plasma'.
- """
- name = str(name)
- if self._name == name:
- return
- if self.isEditable() is False:
- raise NotEditableError('Colormap is not editable')
- if name not in self.getSupportedColormaps():
- raise ValueError("Colormap name '%s' is not supported" % name)
- self._name = name
- self._colors = _getColormap(self._name)
- self.sigChanged.emit()
-
- def getColormapLUT(self, copy=True):
- """Return the list of colors for the colormap or None if not set.
-
- This returns None if the colormap was set with :meth:`setName`.
- Use :meth:`getNColors` to get the colormap LUT for any colormap.
-
- :param bool copy: If true a copy of the numpy array is provided
- :return: the list of colors for the colormap or None if not set
- :rtype: numpy.ndarray or None
- """
- if self._name is None:
- return numpy.array(self._colors, copy=copy)
- else:
- return None
-
- def setColormapLUT(self, colors):
- """Set the colors of the colormap.
-
- :param numpy.ndarray colors: the colors of the LUT.
- If float, it is converted from [0, 1] to uint8 range.
- Otherwise it is casted to uint8.
-
- .. warning: this will set the value of name to None
- """
- if self.isEditable() is False:
- raise NotEditableError('Colormap is not editable')
- assert colors is not None
-
- colors = numpy.array(colors, copy=False)
- if colors.shape == ():
- raise TypeError("An array is expected for 'colors' argument. '%s' was found." % type(colors))
- assert len(colors) != 0
- assert colors.ndim >= 2
- colors.shape = -1, colors.shape[-1]
- self._colors = _arrayToRgba8888(colors)
- self._name = None
- self.sigChanged.emit()
-
- def getNaNColor(self):
- """Returns the color to use for Not-A-Number floating point value.
-
- :rtype: QColor
- """
- return qt.QColor(*self.__nanColor)
-
- def setNaNColor(self, color):
- """Set the color to use for Not-A-Number floating point value.
-
- :param color: RGB(A) color to use for NaN values
- :type color: QColor, str, tuple of uint8 or float in [0., 1.]
- """
- color = (numpy.array(rgba(color)) * 255).astype(numpy.uint8)
- if not numpy.array_equal(self.__nanColor, color):
- self.__nanColor = color
- self.sigChanged.emit()
-
- def getNormalization(self):
- """Return the normalization of the colormap.
-
- See :meth:`setNormalization` for returned values.
-
- :return: the normalization of the colormap
- :rtype: str
- """
- return self._normalization
-
- def setNormalization(self, norm):
- """Set the colormap normalization.
-
- Accepted normalizations: 'log', 'linear', 'sqrt'
-
- :param str norm: the norm to set
- """
- assert norm in self.NORMALIZATIONS
- if self.isEditable() is False:
- raise NotEditableError('Colormap is not editable')
- self._normalization = str(norm)
- self.sigChanged.emit()
-
- def setGammaNormalizationParameter(self, gamma: float) -> None:
- """Set the gamma correction parameter.
-
- Only used for gamma correction normalization.
-
- :param float gamma:
- :raise ValueError: If gamma is not valid
- """
- if gamma < 0. or not numpy.isfinite(gamma):
- raise ValueError("Gamma value not supported")
- if gamma != self.__gamma:
- self.__gamma = gamma
- self.sigChanged.emit()
-
- def getGammaNormalizationParameter(self) -> float:
- """Returns the gamma correction parameter value.
-
- :rtype: float
- """
- return self.__gamma
-
- def getAutoscaleMode(self):
- """Return the autoscale mode of the colormap ('minmax' or 'stddev3')
-
- :rtype: str
- """
- return self._autoscaleMode
-
- def setAutoscaleMode(self, mode):
- """Set the autoscale mode: either 'minmax' or 'stddev3'
-
- :param str mode: the mode to set
- """
- if self.isEditable() is False:
- raise NotEditableError('Colormap is not editable')
- assert mode in self.AUTOSCALE_MODES
- if mode != self._autoscaleMode:
- self._autoscaleMode = mode
- self.sigChanged.emit()
-
- def isAutoscale(self):
- """Return True if both min and max are in autoscale mode"""
- return self._vmin is None and self._vmax is None
-
- def getVMin(self):
- """Return the lower bound of the colormap
-
- :return: the lower bound of the colormap
- :rtype: float or None
- """
- return self._vmin
-
- def setVMin(self, vmin):
- """Set the minimal value of the colormap
-
- :param float vmin: Lower bound of the colormap or None for autoscale
- (default)
- value)
- """
- if self.isEditable() is False:
- raise NotEditableError('Colormap is not editable')
- if vmin is not None:
- if self._vmax is not None and vmin > self._vmax:
- err = "Can't set vmin because vmin >= vmax. " \
- "vmin = %s, vmax = %s" % (vmin, self._vmax)
- raise ValueError(err)
-
- self._vmin = vmin
- self.sigChanged.emit()
-
- def getVMax(self):
- """Return the upper bounds of the colormap or None
-
- :return: the upper bounds of the colormap or None
- :rtype: float or None
- """
- return self._vmax
-
- def setVMax(self, vmax):
- """Set the maximal value of the colormap
-
- :param float vmax: Upper bounds of the colormap or None for autoscale
- (default)
- """
- if self.isEditable() is False:
- raise NotEditableError('Colormap is not editable')
- if vmax is not None:
- if self._vmin is not None and vmax < self._vmin:
- err = "Can't set vmax because vmax <= vmin. " \
- "vmin = %s, vmax = %s" % (self._vmin, vmax)
- raise ValueError(err)
-
- self._vmax = vmax
- self.sigChanged.emit()
-
- def isEditable(self):
- """ Return if the colormap is editable or not
-
- :return: editable state of the colormap
- :rtype: bool
- """
- return self._editable
-
- def setEditable(self, editable):
- """
- Set the editable state of the colormap
-
- :param bool editable: is the colormap editable
- """
- assert type(editable) is bool
- self._editable = editable
- self.sigChanged.emit()
-
- def _getNormalizer(self):
- """Returns normalizer object"""
- normalization = self.getNormalization()
- if normalization == self.GAMMA:
- return _GammaNormalization(self.getGammaNormalizationParameter())
- else:
- return self._BASIC_NORMALIZATIONS[normalization]
-
- def _computeAutoscaleRange(self, data):
- """Compute the data range which will be used in autoscale mode.
-
- :param numpy.ndarray data: The data for which to compute the range
- :return: (vmin, vmax) range
- """
- return self._getNormalizer().autoscale(
- data, mode=self.getAutoscaleMode())
-
- def getColormapRange(self, data=None):
- """Return (vmin, vmax) the range of the colormap for the given data or item.
-
- :param Union[numpy.ndarray,~silx.gui.plot.items.ColormapMixIn] data:
- The data or item to use for autoscale bounds.
- :return: (vmin, vmax) corresponding to the colormap applied to data if provided.
- :rtype: tuple
- """
- vmin = self._vmin
- vmax = self._vmax
- assert vmin is None or vmax is None or vmin <= vmax # TODO handle this in setters
-
- normalizer = self._getNormalizer()
-
- # Handle invalid bounds as autoscale
- if vmin is not None and not normalizer.isValid(vmin):
- _logger.info(
- 'Invalid vmin, switching to autoscale for lower bound')
- vmin = None
- if vmax is not None and not normalizer.isValid(vmax):
- _logger.info(
- 'Invalid vmax, switching to autoscale for upper bound')
- vmax = None
-
- if vmin is None or vmax is None: # Handle autoscale
- from .plot.items.core import ColormapMixIn # avoid cyclic import
- if isinstance(data, ColormapMixIn):
- min_, max_ = data._getColormapAutoscaleRange(self)
- # Make sure min_, max_ are not None
- min_ = normalizer.DEFAULT_RANGE[0] if min_ is None else min_
- max_ = normalizer.DEFAULT_RANGE[1] if max_ is None else max_
- else:
- min_, max_ = normalizer.autoscale(
- data, mode=self.getAutoscaleMode())
-
- if vmin is None: # Set vmin respecting provided vmax
- vmin = min_ if vmax is None else min(min_, vmax)
-
- if vmax is None:
- vmax = max(max_, vmin) # Handle max_ <= 0 for log scale
-
- return vmin, vmax
-
- def getVRange(self):
- """Get the bounds of the colormap
-
- :rtype: Tuple(Union[float,None],Union[float,None])
- :returns: A tuple of 2 values for min and max. Or None instead of float
- for autoscale
- """
- return self.getVMin(), self.getVMax()
-
- def setVRange(self, vmin, vmax):
- """Set the bounds of the colormap
-
- :param vmin: Lower bound of the colormap or None for autoscale
- (default)
- :param vmax: Upper bounds of the colormap or None for autoscale
- (default)
- """
- if self.isEditable() is False:
- raise NotEditableError('Colormap is not editable')
- if vmin is not None and vmax is not None:
- if vmin > vmax:
- err = "Can't set vmin and vmax because vmin >= vmax " \
- "vmin = %s, vmax = %s" % (vmin, vmax)
- raise ValueError(err)
-
- if self._vmin == vmin and self._vmax == vmax:
- return
-
- self._vmin = vmin
- self._vmax = vmax
- self.sigChanged.emit()
-
- def __getitem__(self, item):
- if item == 'autoscale':
- return self.isAutoscale()
- elif item == 'name':
- return self.getName()
- elif item == 'normalization':
- return self.getNormalization()
- elif item == 'vmin':
- return self.getVMin()
- elif item == 'vmax':
- return self.getVMax()
- elif item == 'colors':
- return self.getColormapLUT()
- elif item == 'autoscaleMode':
- return self.getAutoscaleMode()
- else:
- raise KeyError(item)
-
- def _toDict(self):
- """Return the equivalent colormap as a dictionary
- (old colormap representation)
-
- :return: the representation of the Colormap as a dictionary
- :rtype: dict
- """
- return {
- 'name': self._name,
- 'colors': self.getColormapLUT(),
- 'vmin': self._vmin,
- 'vmax': self._vmax,
- 'autoscale': self.isAutoscale(),
- 'normalization': self.getNormalization(),
- 'autoscaleMode': self.getAutoscaleMode(),
- }
-
- def _setFromDict(self, dic):
- """Set values to the colormap from a dictionary
-
- :param dict dic: the colormap as a dictionary
- """
- if self.isEditable() is False:
- raise NotEditableError('Colormap is not editable')
- name = dic['name'] if 'name' in dic else None
- colors = dic['colors'] if 'colors' in dic else None
- if name is not None and colors is not None:
- if isinstance(colors, int):
- # Filter out argument which was supported but never used
- _logger.info("Unused 'colors' from colormap dictionary filterer.")
- colors = None
- vmin = dic['vmin'] if 'vmin' in dic else None
- vmax = dic['vmax'] if 'vmax' in dic else None
- if 'normalization' in dic:
- normalization = dic['normalization']
- else:
- warn = 'Normalization not given in the dictionary, '
- warn += 'set by default to ' + Colormap.LINEAR
- _logger.warning(warn)
- normalization = Colormap.LINEAR
-
- if name is None and colors is None:
- err = 'The colormap should have a name defined or a tuple of colors'
- raise ValueError(err)
- if normalization not in Colormap.NORMALIZATIONS:
- err = 'Given normalization is not recognized (%s)' % normalization
- raise ValueError(err)
-
- autoscaleMode = dic.get('autoscaleMode', Colormap.MINMAX)
- if autoscaleMode not in Colormap.AUTOSCALE_MODES:
- err = 'Given autoscale mode is not recognized (%s)' % autoscaleMode
- raise ValueError(err)
-
- # If autoscale, then set boundaries to None
- if dic.get('autoscale', False):
- vmin, vmax = None, None
-
- if name is not None:
- self.setName(name)
- else:
- self.setColormapLUT(colors)
- self._vmin = vmin
- self._vmax = vmax
- self._autoscale = True if (vmin is None and vmax is None) else False
- self._normalization = normalization
- self._autoscaleMode = autoscaleMode
-
- self.sigChanged.emit()
-
- @staticmethod
- def _fromDict(dic):
- colormap = Colormap()
- colormap._setFromDict(dic)
- return colormap
-
- def copy(self):
- """Return a copy of the Colormap.
-
- :rtype: silx.gui.colors.Colormap
- """
- colormap = Colormap(name=self._name,
- colors=self.getColormapLUT(),
- vmin=self._vmin,
- vmax=self._vmax,
- normalization=self.getNormalization(),
- autoscaleMode=self.getAutoscaleMode())
- colormap.setNaNColor(self.getNaNColor())
- colormap.setGammaNormalizationParameter(
- self.getGammaNormalizationParameter())
- colormap.setEditable(self.isEditable())
- return colormap
-
- def applyToData(self, data, reference=None):
- """Apply the colormap to the data
-
- :param Union[numpy.ndarray,~silx.gui.plot.item.ColormapMixIn] data:
- The data to convert or the item for which to apply the colormap.
- :param Union[numpy.ndarray,~silx.gui.plot.item.ColormapMixIn,None] reference:
- The data or item to use as reference to compute autoscale
- """
- if reference is None:
- reference = data
- vmin, vmax = self.getColormapRange(reference)
-
- if hasattr(data, "getColormappedData"): # Use item's data
- data = data.getColormappedData(copy=False)
-
- return _colormap.cmap(
- data,
- self._colors,
- vmin,
- vmax,
- self._getNormalizer(),
- self.__nanColor)
-
- @staticmethod
- def getSupportedColormaps():
- """Get the supported colormap names as a tuple of str.
-
- The list should at least contain and start by:
-
- ('gray', 'reversed gray', 'temperature', 'red', 'green', 'blue',
- 'viridis', 'magma', 'inferno', 'plasma')
-
- :rtype: tuple
- """
- colormaps = set()
- if _matplotlib_colormaps is not None:
- colormaps.update(_matplotlib_colormaps())
- colormaps.update(_AVAILABLE_LUTS.keys())
-
- colormaps = tuple(cmap for cmap in sorted(colormaps)
- if cmap not in _AVAILABLE_LUTS.keys())
-
- return tuple(_AVAILABLE_LUTS.keys()) + colormaps
-
- def __str__(self):
- return str(self._toDict())
-
- def __eq__(self, other):
- """Compare colormap values and not pointers"""
- if other is None:
- return False
- if not isinstance(other, Colormap):
- return False
- if self.getNormalization() != other.getNormalization():
- return False
- if self.getNormalization() == self.GAMMA:
- delta = self.getGammaNormalizationParameter() - other.getGammaNormalizationParameter()
- if abs(delta) > 0.001:
- return False
- return (self.getName() == other.getName() and
- self.getAutoscaleMode() == other.getAutoscaleMode() and
- self.getVMin() == other.getVMin() and
- self.getVMax() == other.getVMax() and
- numpy.array_equal(self.getColormapLUT(), other.getColormapLUT())
- )
-
- _SERIAL_VERSION = 3
-
- def restoreState(self, byteArray):
- """
- Read the colormap state from a QByteArray.
-
- :param qt.QByteArray byteArray: Stream containing the state
- :return: True if the restoration sussseed
- :rtype: bool
- """
- if self.isEditable() is False:
- raise NotEditableError('Colormap is not editable')
- stream = qt.QDataStream(byteArray, qt.QIODevice.ReadOnly)
-
- className = stream.readQString()
- if className != self.__class__.__name__:
- _logger.warning("Classname mismatch. Found %s." % className)
- return False
-
- version = stream.readUInt32()
- if version not in numpy.arange(1, self._SERIAL_VERSION+1):
- _logger.warning("Serial version mismatch. Found %d." % version)
- return False
-
- name = stream.readQString()
- isNull = stream.readBool()
- if not isNull:
- vmin = stream.readQVariant()
- else:
- vmin = None
- isNull = stream.readBool()
- if not isNull:
- vmax = stream.readQVariant()
- else:
- vmax = None
-
- normalization = stream.readQString()
- if normalization == Colormap.GAMMA:
- gamma = stream.readFloat()
- else:
- gamma = None
-
- if version == 1:
- autoscaleMode = Colormap.MINMAX
- else:
- autoscaleMode = stream.readQString()
-
- if version <= 2:
- nanColor = self._DEFAULT_NAN_COLOR
- else:
- nanColor = stream.readInt32(), stream.readInt32(), stream.readInt32(), stream.readInt32()
-
- # emit change event only once
- old = self.blockSignals(True)
- try:
- self.setName(name)
- self.setNormalization(normalization)
- self.setAutoscaleMode(autoscaleMode)
- self.setVRange(vmin, vmax)
- if gamma is not None:
- self.setGammaNormalizationParameter(gamma)
- self.setNaNColor(nanColor)
- finally:
- self.blockSignals(old)
- self.sigChanged.emit()
- return True
-
- def saveState(self):
- """
- Save state of the colomap into a QDataStream.
-
- :rtype: qt.QByteArray
- """
- data = qt.QByteArray()
- stream = qt.QDataStream(data, qt.QIODevice.WriteOnly)
-
- stream.writeQString(self.__class__.__name__)
- stream.writeUInt32(self._SERIAL_VERSION)
- stream.writeQString(self.getName())
- stream.writeBool(self.getVMin() is None)
- if self.getVMin() is not None:
- stream.writeQVariant(self.getVMin())
- stream.writeBool(self.getVMax() is None)
- if self.getVMax() is not None:
- stream.writeQVariant(self.getVMax())
- stream.writeQString(self.getNormalization())
- if self.getNormalization() == Colormap.GAMMA:
- stream.writeFloat(self.getGammaNormalizationParameter())
- stream.writeQString(self.getAutoscaleMode())
- nanColor = self.getNaNColor()
- stream.writeInt32(nanColor.red())
- stream.writeInt32(nanColor.green())
- stream.writeInt32(nanColor.blue())
- stream.writeInt32(nanColor.alpha())
-
- return data
-
-
-_PREFERRED_COLORMAPS = None
-"""
-Tuple of preferred colormap names accessed with :meth:`preferredColormaps`.
-"""
-
-
-def preferredColormaps():
- """Returns the name of the preferred colormaps.
-
- This list is used by widgets allowing to change the colormap
- like the :class:`ColormapDialog` as a subset of colormap choices.
-
- :rtype: tuple of str
- """
- global _PREFERRED_COLORMAPS
- if _PREFERRED_COLORMAPS is None:
- # Initialize preferred colormaps
- default_preferred = []
- for name, info in _AVAILABLE_LUTS.items():
- if (info.preferred and
- (info.source != 'matplotlib' or _matplotlib_cm is not None)):
- default_preferred.append(name)
- setPreferredColormaps(default_preferred)
- return tuple(_PREFERRED_COLORMAPS)
-
-
-def setPreferredColormaps(colormaps):
- """Set the list of preferred colormap names.
-
- Warning: If a colormap name is not available
- it will be removed from the list.
-
- :param colormaps: Not empty list of colormap names
- :type colormaps: iterable of str
- :raise ValueError: if the list of available preferred colormaps is empty.
- """
- supportedColormaps = Colormap.getSupportedColormaps()
- colormaps = [cmap for cmap in colormaps if cmap in supportedColormaps]
- if len(colormaps) == 0:
- raise ValueError("Cannot set preferred colormaps to an empty list")
-
- global _PREFERRED_COLORMAPS
- _PREFERRED_COLORMAPS = colormaps
-
-
-def registerLUT(name, colors, cursor_color='black', preferred=True):
- """Register a custom LUT to be used with `Colormap` objects.
-
- It can override existing LUT names.
-
- :param str name: Name of the LUT as defined to configure colormaps
- :param numpy.ndarray colors: The custom LUT to register.
- Nx3 or Nx4 numpy array of RGB(A) colors,
- either uint8 or float in [0, 1].
- :param bool preferred: If true, this LUT will be displayed as part of the
- preferred colormaps in dialogs.
- :param str cursor_color: Color used to display overlay over images using
- colormap with this LUT.
- """
- description = _LUT_DESCRIPTION('user', cursor_color, preferred=preferred)
- colors = _arrayToRgba8888(colors)
- _AVAILABLE_LUTS[name] = description
-
- if preferred:
- # Invalidate the preferred cache
- global _PREFERRED_COLORMAPS
- if _PREFERRED_COLORMAPS is not None:
- if name not in _PREFERRED_COLORMAPS:
- _PREFERRED_COLORMAPS.append(name)
- else:
- # The cache is not yet loaded, it's fine
- pass
-
- # Register the cache as the LUT was already loaded
- _COLORMAP_CACHE[name] = colors
diff --git a/silx/gui/console.py b/silx/gui/console.py
deleted file mode 100644
index 5dc6336..0000000
--- a/silx/gui/console.py
+++ /dev/null
@@ -1,202 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2004-2019 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""This module provides an IPython console widget.
-
-You can push variables - any python object - to the
-console's interactive namespace. This provides users with an advanced way
-of interacting with your program. For instance, if your program has a
-:class:`PlotWidget` or a :class:`PlotWindow`, you can push a reference to
-these widgets to allow your users to add curves, save data to files… by using
-the widgets' methods from the console.
-
-.. note::
-
- This module has a dependency on
- `qtconsole <https://pypi.org/project/qtconsole/>`_.
- An ``ImportError`` will be raised if it is
- imported while the dependencies are not satisfied.
-
-Basic usage example::
-
- from silx.gui import qt
- from silx.gui.console import IPythonWidget
-
- app = qt.QApplication([])
-
- hello_button = qt.QPushButton("Hello World!", None)
- hello_button.show()
-
- console = IPythonWidget()
- console.show()
- console.pushVariables({"the_button": hello_button})
-
- app.exec_()
-
-This program will display a console widget and a push button in two separate
-windows. You will be able to interact with the button from the console,
-for example change its text::
-
- >>> the_button.setText("Spam spam")
-
-An IPython interactive console is a powerful tool that enables you to work
-with data and plot it.
-See `this tutorial <https://plot.ly/python/ipython-notebook-tutorial/>`_
-for more information on some of the rich features of IPython.
-"""
-__authors__ = ["Tim Rae", "V.A. Sole", "P. Knobel"]
-__license__ = "MIT"
-__date__ = "24/05/2016"
-
-import logging
-
-from . import qt
-
-_logger = logging.getLogger(__name__)
-
-
-# This widget cannot be used inside an interactive IPython shell.
-# It would raise MultipleInstanceError("Multiple incompatible subclass
-# instances of InProcessInteractiveShell are being created").
-try:
- __IPYTHON__
-except NameError:
- pass # Not in IPython
-else:
- msg = "Module " + __name__ + " cannot be used within an IPython shell"
- raise ImportError(msg)
-
-try:
- from qtconsole.rich_jupyter_widget import RichJupyterWidget as \
- _RichJupyterWidget
-except ImportError:
- try:
- from qtconsole.rich_ipython_widget import RichJupyterWidget as \
- _RichJupyterWidget
- except ImportError:
- from qtconsole.rich_ipython_widget import RichIPythonWidget as \
- _RichJupyterWidget
-
-from qtconsole.inprocess import QtInProcessKernelManager
-
-try:
- from ipykernel import version_info as _ipykernel_version_info
-except ImportError:
- _ipykernel_version_info = None
-
-
-class IPythonWidget(_RichJupyterWidget):
- """Live IPython console widget.
-
- .. image:: img/IPythonWidget.png
-
- :param custom_banner: Custom welcome message to be printed at the top of
- the console.
- """
-
- def __init__(self, parent=None, custom_banner=None, *args, **kwargs):
- if parent is not None:
- kwargs["parent"] = parent
- super(IPythonWidget, self).__init__(*args, **kwargs)
- if custom_banner is not None:
- self.banner = custom_banner
- self.setWindowTitle(self.banner)
- self.kernel_manager = kernel_manager = QtInProcessKernelManager()
- kernel_manager.start_kernel()
-
- # Monkey-patch to workaround issue:
- # https://github.com/ipython/ipykernel/issues/370
- if (_ipykernel_version_info is not None and
- _ipykernel_version_info[0] > 4 and
- _ipykernel_version_info[:3] <= (5, 1, 0)):
- def _abort_queues(*args, **kwargs):
- pass
- kernel_manager.kernel._abort_queues = _abort_queues
-
- self.kernel_client = kernel_client = self._kernel_manager.client()
- kernel_client.start_channels()
-
- def stop():
- kernel_client.stop_channels()
- kernel_manager.shutdown_kernel()
- self.exit_requested.connect(stop)
-
- def sizeHint(self):
- """Return a reasonable default size for usage in :class:`PlotWindow`"""
- return qt.QSize(500, 300)
-
- def pushVariables(self, variable_dict):
- """ Given a dictionary containing name / value pairs, push those
- variables to the IPython console widget.
-
- :param variable_dict: Dictionary of variables to be pushed to the
- console's interactive namespace (```{variable_name: object, …}```)
- """
- self.kernel_manager.kernel.shell.push(variable_dict)
-
-
-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
- the console
- :param title: Dock widget title
- :param parent: Parent :class:`qt.QMainWindow` containing this
- :class:`qt.QDockWidget`
- """
- def __init__(self, parent=None, available_vars=None, custom_banner=None,
- title="Console"):
- super(IPythonDockWidget, self).__init__(title, parent)
-
- self.ipyconsole = IPythonWidget(custom_banner=custom_banner)
-
- self.layout().setContentsMargins(0, 0, 0, 0)
- self.setWidget(self.ipyconsole)
-
- if available_vars is not None:
- self.ipyconsole.pushVariables(available_vars)
-
- def showEvent(self, event):
- """Make sure this widget is raised when it is shown
- (when it is first created as a tab in PlotWindow or when it is shown
- again after hiding).
- """
- self.raise_()
-
-
-def main():
- """Run a Qt app with an IPython console"""
- app = qt.QApplication([])
- widget = IPythonDockWidget()
- widget.show()
- app.exec_()
-
-
-if __name__ == '__main__':
- main()
diff --git a/silx/gui/data/ArrayTableModel.py b/silx/gui/data/ArrayTableModel.py
deleted file mode 100644
index b7bd9c4..0000000
--- a/silx/gui/data/ArrayTableModel.py
+++ /dev/null
@@ -1,670 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016-2021 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 a data model for displaying and editing arrays of any
-number of dimensions in a table view.
-"""
-from __future__ import division
-import numpy
-import logging
-from silx.gui import qt
-from silx.gui.data.TextFormatter import TextFormatter
-
-__authors__ = ["V.A. Sole"]
-__license__ = "MIT"
-__date__ = "27/09/2017"
-
-
-_logger = logging.getLogger(__name__)
-
-
-def _is_array(data):
- """Return True if object implements all necessary attributes to be used
- as a numpy array.
-
- :param object data: Array-like object (numpy array, h5py dataset...)
- :return: boolean
- """
- # add more required attribute if necessary
- for attr in ("shape", "dtype"):
- if not hasattr(data, attr):
- return False
- return True
-
-
-class ArrayTableModel(qt.QAbstractTableModel):
- """This data model provides access to 2D slices in a N-dimensional
- array.
-
- A slice for a 3-D array is characterized by a perspective (the number of
- the axis orthogonal to the slice) and an index at which the slice
- intersects the orthogonal axis.
-
- In the n-D case, only slices parallel to the last two axes are handled. A
- slice is therefore characterized by a list of indices locating the
- slice on all the :math:`n - 2` orthogonal axes.
-
- :param parent: Parent QObject
- :param data: Numpy array, or object implementing a similar interface
- (e.g. h5py dataset)
- :param str fmt: Format string for representing numerical values.
- Default is ``"%g"``.
- :param sequence[int] perspective: See documentation
- of :meth:`setPerspective`.
- """
-
- MAX_NUMBER_OF_SECTIONS = 10e6
- """Maximum number of displayed rows and columns"""
-
- def __init__(self, parent=None, data=None, perspective=None):
- qt.QAbstractTableModel.__init__(self, parent)
-
- self._array = None
- """n-dimensional numpy array"""
-
- self._bgcolors = None
- """(n+1)-dimensional numpy array containing RGB(A) color data
- for the background color
- """
-
- self._fgcolors = None
- """(n+1)-dimensional numpy array containing RGB(A) color data
- for the foreground color
- """
-
- self._formatter = None
- """Formatter for text representation of data"""
-
- formatter = TextFormatter(self)
- formatter.setUseQuoteForText(False)
- self.setFormatter(formatter)
-
- self._index = None
- """This attribute stores the slice index, as a list of indices
- where the frame intersects orthogonal axis."""
-
- self._perspective = None
- """Sequence of dimensions orthogonal to the frame to be viewed.
- For an array with ``n`` dimensions, this is a sequence of ``n-2``
- integers. the first dimension is numbered ``0``.
- By default, the data frames use the last two dimensions as their axes
- and therefore the perspective is a sequence of the first ``n-2``
- dimensions.
- For example, for a 5-D array, the default perspective is ``(0, 1, 2)``
- and the default frames axes are ``(3, 4)``."""
-
- # set _data and _perspective
- self.setArrayData(data, perspective=perspective)
-
- def _getRowDim(self):
- """The row axis is the first axis parallel to the frames
- (lowest dimension number)
-
- Return None for 0-D (scalar) or 1-D arrays
- """
- n_dimensions = len(self._array.shape)
- if n_dimensions < 2:
- # scalar or 1D array: no row index
- return None
- # take all dimensions and remove the orthogonal ones
- frame_axes = set(range(0, n_dimensions)) - set(self._perspective)
- # sanity check
- assert len(frame_axes) == 2
- return min(frame_axes)
-
- def _getColumnDim(self):
- """The column axis is the second (highest dimension) axis parallel
- to the frames
-
- Return None for 0-D (scalar)
- """
- n_dimensions = len(self._array.shape)
- if n_dimensions < 1:
- # scalar: no column index
- return None
- frame_axes = set(range(0, n_dimensions)) - set(self._perspective)
- # sanity check
- assert (len(frame_axes) == 2) if n_dimensions > 1 else (len(frame_axes) == 1)
- return max(frame_axes)
-
- def _getIndexTuple(self, table_row, table_col):
- """Return the n-dimensional index of a value in the original array,
- based on its row and column indices in the table view
-
- :param table_row: Row index (0-based) of a table cell
- :param table_col: Column index (0-based) of a table cell
- :return: Tuple of indices of the element in the numpy array
- """
- row_dim = self._getRowDim()
- col_dim = self._getColumnDim()
-
- # get indices on all orthogonal axes
- selection = list(self._index)
- # insert indices on parallel axes
- if row_dim is not None:
- selection.insert(row_dim, table_row)
- if col_dim is not None:
- selection.insert(col_dim, table_col)
- return tuple(selection)
-
- # Methods to be implemented to subclass QAbstractTableModel
- def rowCount(self, parent_idx=None):
- """QAbstractTableModel method
- Return number of rows to be displayed in table"""
- row_dim = self._getRowDim()
- if row_dim is None:
- # 0-D and 1-D arrays
- return 1
- return min(self._array.shape[row_dim], self.MAX_NUMBER_OF_SECTIONS)
-
- def columnCount(self, parent_idx=None):
- """QAbstractTableModel method
- Return number of columns to be displayed in table"""
- col_dim = self._getColumnDim()
- if col_dim is None:
- # 0-D array
- return 1
- return min(self._array.shape[col_dim], self.MAX_NUMBER_OF_SECTIONS)
-
- def __isClipped(self, orientation=qt.Qt.Vertical) -> bool:
- """Returns whether or not array is clipped in a given orientation"""
- if orientation == qt.Qt.Vertical:
- dim = self._getRowDim()
- else:
- dim = self._getColumnDim()
- return (dim is not None and
- self._array.shape[dim] > self.MAX_NUMBER_OF_SECTIONS)
-
- def __isClippedIndex(self, index) -> bool:
- """Returns whether or not index's cell represents clipped data."""
- if not index.isValid():
- return False
- if index.row() == self.MAX_NUMBER_OF_SECTIONS - 2:
- return self.__isClipped(qt.Qt.Vertical)
- if index.column() == self.MAX_NUMBER_OF_SECTIONS - 2:
- return self.__isClipped(qt.Qt.Horizontal)
- return False
-
- def __clippedData(self, role=qt.Qt.DisplayRole):
- """Return data for cells representing clipped data"""
- if role == qt.Qt.DisplayRole:
- return "..."
- elif role == qt.Qt.ToolTipRole:
- return "Dataset is too large: display is clipped"
- else:
- return None
-
- def data(self, index, role=qt.Qt.DisplayRole):
- """QAbstractTableModel method to access data values
- in the format ready to be displayed"""
- if index.isValid():
- if self.__isClippedIndex(index): # Special displayed for clipped data
- return self.__clippedData(role)
-
- row, column = index.row(), index.column()
-
- # When clipped, display last data of the array in last column of the table
- if (self.__isClipped(qt.Qt.Vertical) and
- row == self.MAX_NUMBER_OF_SECTIONS - 1):
- row = self._array.shape[self._getRowDim()] - 1
- if (self.__isClipped(qt.Qt.Horizontal) and
- column == self.MAX_NUMBER_OF_SECTIONS - 1):
- column = self._array.shape[self._getColumnDim()] - 1
-
- selection = self._getIndexTuple(row, column)
-
- if role == qt.Qt.DisplayRole:
- 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]
- if self._bgcolors.shape[-1] == 3:
- return qt.QColor(r, g, b)
- if self._bgcolors.shape[-1] == 4:
- a = self._bgcolors[selection][3]
- return qt.QColor(r, g, b, a)
-
- if role == qt.Qt.ForegroundRole:
- if self._fgcolors is not None:
- r, g, b = self._fgcolors[selection][0:3]
- if self._fgcolors.shape[-1] == 3:
- return qt.QColor(r, g, b)
- if self._fgcolors.shape[-1] == 4:
- a = self._fgcolors[selection][3]
- return qt.QColor(r, g, b, a)
-
- # no fg color given, use black or white
- # based on luminosity threshold
- elif self._bgcolors is not None:
- r, g, b = self._bgcolors[selection][0:3]
- lum = 0.21 * r + 0.72 * g + 0.07 * b
- if lum < 128:
- return qt.QColor(qt.Qt.white)
- else:
- return qt.QColor(qt.Qt.black)
-
- def headerData(self, section, orientation, role=qt.Qt.DisplayRole):
- """QAbstractTableModel method
- Return the 0-based row or column index, for display in the
- horizontal and vertical headers"""
- if self.__isClipped(orientation): # Header is clipped
- if section == self.MAX_NUMBER_OF_SECTIONS - 2:
- # Represent clipped data
- return self.__clippedData(role)
-
- elif section == self.MAX_NUMBER_OF_SECTIONS - 1:
- # Display last index from data not table
- if role == qt.Qt.DisplayRole:
- if orientation == qt.Qt.Vertical:
- dim = self._getRowDim()
- else:
- dim = self._getColumnDim()
- return str(self._array.shape[dim] - 1)
- else:
- return None
-
- if role == qt.Qt.DisplayRole:
- return "%d" % section
- return None
-
- def flags(self, index):
- """QAbstractTableModel method to inform the view whether data
- is editable or not."""
- if not self._editable or self.__isClippedIndex(index):
- return qt.QAbstractTableModel.flags(self, index)
- return qt.QAbstractTableModel.flags(self, index) | qt.Qt.ItemIsEditable
-
- def setData(self, index, value, role=None):
- """QAbstractTableModel method to handle editing data.
- Cast the new value into the same format as the array before editing
- the array value."""
- if index.isValid() and role == qt.Qt.EditRole:
- try:
- # cast value to same type as array
- v = numpy.array(value, dtype=self._array.dtype).item()
- except ValueError:
- return False
-
- selection = self._getIndexTuple(index.row(),
- index.column())
- self._array[selection] = v
- self.dataChanged.emit(index, index)
- return True
- else:
- return False
-
- # Public methods
- def setArrayData(self, data, copy=True,
- perspective=None, editable=False):
- """Set the data array and the viewing perspective.
-
- You can set ``copy=False`` if you need more performances, when dealing
- with a large numpy array. In this case, a simple reference to the data
- is used to access the data, rather than a copy of the array.
-
- .. warning::
-
- Any change to the data model will affect your original data
- array, when using a reference rather than a copy..
-
- :param data: n-dimensional numpy array, or any object that can be
- converted to a numpy array using ``numpy.array(data)`` (e.g.
- a nested sequence).
- :param bool copy: If *True* (default), a copy of the array is stored
- and the original array is not modified if the table is edited.
- If *False*, then the behavior depends on the data type:
- if possible (if the original array is a proper numpy array)
- a reference to the original array is used.
- :param perspective: See documentation of :meth:`setPerspective`.
- If None, the default perspective is the list of the first ``n-2``
- dimensions, to view frames parallel to the last two axes.
- :param bool editable: Flag to enable editing data. Default *False*.
- """
- if qt.qVersion() > "4.6":
- self.beginResetModel()
- else:
- self.reset()
-
- if data is None:
- # empty array
- self._array = numpy.array([])
- 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" +
- " (this will cause the data to be copied!)")
- # # copy not requested, but necessary
- # _logger.warning(
- # "data is not an array-like object. " +
- # "Data must be copied.")
- # self._array = numpy.array(data, copy=True)
- else:
- # Copy explicitly disabled & data implements required attributes.
- # We can use a reference.
- self._array = data
-
- # reset colors to None if new data shape is inconsistent
- valid_color_shapes = (self._array.shape + (3,),
- self._array.shape + (4,))
- if self._bgcolors is not None:
- if self._bgcolors.shape not in valid_color_shapes:
- self._bgcolors = None
- if self._fgcolors is not None:
- if self._fgcolors.shape not in valid_color_shapes:
- self._fgcolors = None
-
- self.setEditable(editable)
-
- self._index = [0 for _i in range((len(self._array.shape) - 2))]
- self._perspective = tuple(perspective) if perspective is not None else\
- tuple(range(0, len(self._array.shape) - 2))
-
- if qt.qVersion() > "4.6":
- self.endResetModel()
-
- def setArrayColors(self, bgcolors=None, fgcolors=None):
- """Set the colors for all table cells by passing an array
- of RGB or RGBA values (integers between 0 and 255).
-
- The shape of the colors array must be consistent with the data shape.
-
- If the data array is n-dimensional, the colors array must be
- (n+1)-dimensional, with the first n-dimensions identical to the data
- array dimensions, and the last dimension length-3 (RGB) or
- length-4 (RGBA).
-
- :param bgcolors: RGB or RGBA colors array, defining the background color
- for each cell in the table.
- :param fgcolors: RGB or RGBA colors array, defining the foreground color
- (text color) for each cell in the table.
- """
- # array must be RGB or RGBA
- valid_shapes = (self._array.shape + (3,), self._array.shape + (4,))
- errmsg = "Inconsistent shape for color array, should be %s or %s" % valid_shapes
-
- if bgcolors is not None:
- if not _is_array(bgcolors):
- bgcolors = numpy.array(bgcolors)
- assert bgcolors.shape in valid_shapes, errmsg
-
- self._bgcolors = bgcolors
-
- if fgcolors is not None:
- if not _is_array(fgcolors):
- fgcolors = numpy.array(fgcolors)
- assert fgcolors.shape in valid_shapes, errmsg
-
- self._fgcolors = fgcolors
-
- def setEditable(self, editable):
- """Set flags to make the data editable.
-
- .. warning::
-
- If the data is a reference to a h5py dataset open in read-only
- mode, setting *editable=True* will fail and print a warning.
-
- .. warning::
-
- Making the data editable means that the underlying data structure
- in this data model will be modified.
- If the data is a reference to a public object (open with
- ``copy=False``), this could have side effects. If it is a
- reference to an HDF5 dataset, this means the file will be
- modified.
-
- :param bool editable: Flag to enable editing data.
- :return: True if setting desired flag succeeded, False if it failed.
- """
- self._editable = editable
- if hasattr(self._array, "file"):
- if hasattr(self._array.file, "mode"):
- if editable and self._array.file.mode == "r":
- _logger.warning(
- "Data is a HDF5 dataset open in read-only " +
- "mode. Editing must be disabled.")
- self._editable = False
- return False
- return True
-
- def getData(self, copy=True):
- """Return a copy of the data array, or a reference to it
- if *copy=False* is passed as parameter.
-
- In case the shape was modified, to convert 0-D or 1-D data
- into 2-D data, the original shape is restored in the returned data.
-
- :param bool copy: If *True* (default), return a copy of the data. If
- *False*, return a reference.
- :return: numpy array of data, or reference to original data object
- if *copy=False*
- """
- data = self._array if not copy else numpy.array(self._array, copy=True)
- return data
-
- def setFrameIndex(self, index):
- """Set the active slice index.
-
- This method is only relevant to arrays with at least 3 dimensions.
-
- :param index: Index of the active slice in the array.
- In the general n-D case, this is a sequence of :math:`n - 2`
- indices where the slice intersects the respective orthogonal axes.
- :raise IndexError: If any index in the index sequence is out of bound
- on its respective axis.
- """
- shape = self._array.shape
- if len(shape) < 3:
- # index is ignored
- return
-
- if qt.qVersion() > "4.6":
- self.beginResetModel()
- else:
- self.reset()
-
- if len(shape) == 3:
- len_ = shape[self._perspective[0]]
- # accept integers as index in the case of 3-D arrays
- if not hasattr(index, "__len__"):
- self._index = [index]
- else:
- self._index = index
- if not 0 <= self._index[0] < len_:
- raise ValueError("Index must be a positive integer " +
- "lower than %d" % len_)
- else:
- # general n-D case
- for i_, idx in enumerate(index):
- if not 0 <= idx < shape[self._perspective[i_]]:
- raise IndexError("Invalid index %d " % idx +
- "not in range 0-%d" % (shape[i_] - 1))
- self._index = index
-
- if qt.qVersion() > "4.6":
- self.endResetModel()
-
- def setFormatter(self, formatter):
- """Set the formatter object to be used to display data from the model
-
- :param TextFormatter formatter: Formatter to use
- """
- if formatter is self._formatter:
- return
-
- if qt.qVersion() > "4.6":
- self.beginResetModel()
-
- if self._formatter is not None:
- self._formatter.formatChanged.disconnect(self.__formatChanged)
-
- self._formatter = formatter
- if self._formatter is not None:
- self._formatter.formatChanged.connect(self.__formatChanged)
-
- if qt.qVersion() > "4.6":
- self.endResetModel()
- else:
- self.reset()
-
- def getFormatter(self):
- """Returns the text formatter used.
-
- :rtype: TextFormatter
- """
- return self._formatter
-
- def __formatChanged(self):
- """Called when the format changed.
- """
- self.reset()
-
- def setPerspective(self, perspective):
- """Set the perspective by defining a sequence listing all axes
- orthogonal to the frame or 2-D slice to be visualized.
-
- Alternatively, you can use :meth:`setFrameAxes` for the complementary
- approach of specifying the two axes parallel to the frame.
-
- In the 1-D or 2-D case, this parameter is irrelevant.
-
- In the 3-D case, if the unit vectors describing
- your axes are :math:`\vec{x}, \vec{y}, \vec{z}`, a perspective of 0
- means you slices are parallel to :math:`\vec{y}\vec{z}`, 1 means they
- are parallel to :math:`\vec{x}\vec{z}` and 2 means they
- are parallel to :math:`\vec{x}\vec{y}`.
-
- In the n-D case, this parameter is a sequence of :math:`n-2` axes
- numbers.
- For instance if you want to display 2-D frames whose axes are the
- second and third dimensions of a 5-D array, set the perspective to
- ``(0, 3, 4)``.
-
- :param perspective: Sequence of dimensions/axes orthogonal to the
- frames.
- :raise: IndexError if any value in perspective is higher than the
- number of dimensions minus one (first dimension is 0), or
- if the number of values is different from the number of dimensions
- minus two.
- """
- n_dimensions = len(self._array.shape)
- if n_dimensions < 3:
- _logger.warning(
- "perspective is not relevant for 1D and 2D arrays")
- return
-
- if not hasattr(perspective, "__len__"):
- # we can tolerate an integer for 3-D array
- if n_dimensions == 3:
- perspective = [perspective]
- else:
- raise ValueError("perspective must be a sequence of integers")
-
- # ensure unicity of dimensions in perspective
- perspective = tuple(set(perspective))
-
- if len(perspective) != n_dimensions - 2 or\
- min(perspective) < 0 or max(perspective) >= n_dimensions:
- raise IndexError(
- "Invalid perspective " + str(perspective) +
- " for %d-D array " % n_dimensions +
- "with shape " + str(self._array.shape))
-
- if qt.qVersion() > "4.6":
- self.beginResetModel()
- else:
- self.reset()
-
- self._perspective = perspective
-
- # reset index
- self._index = [0 for _i in range(n_dimensions - 2)]
-
- if qt.qVersion() > "4.6":
- self.endResetModel()
-
- def setFrameAxes(self, row_axis, col_axis):
- """Set the perspective by specifying the two axes parallel to the frame
- to be visualised.
-
- The complementary approach of defining the orthogonal axes can be used
- with :meth:`setPerspective`.
-
- :param int row_axis: Index (0-based) of the first dimension used as a frame
- axis
- :param int col_axis: Index (0-based) of the 2nd dimension used as a frame
- axis
- :raise: IndexError if axes are invalid
- """
- if row_axis > col_axis:
- _logger.warning("The dimension of the row axis must be lower " +
- "than the dimension of the column axis. Swapping.")
- row_axis, col_axis = min(row_axis, col_axis), max(row_axis, col_axis)
-
- n_dimensions = len(self._array.shape)
- if n_dimensions < 3:
- _logger.warning(
- "Frame axes cannot be changed for 1D and 2D arrays")
- return
-
- perspective = tuple(set(range(0, n_dimensions)) - {row_axis, col_axis})
-
- if len(perspective) != n_dimensions - 2 or\
- min(perspective) < 0 or max(perspective) >= n_dimensions:
- raise IndexError(
- "Invalid perspective " + str(perspective) +
- " for %d-D array " % n_dimensions +
- "with shape " + str(self._array.shape))
-
- if qt.qVersion() > "4.6":
- self.beginResetModel()
- else:
- self.reset()
-
- self._perspective = perspective
- # reset index
- self._index = [0 for _i in range(n_dimensions - 2)]
-
- if qt.qVersion() > "4.6":
- self.endResetModel()
-
-
-if __name__ == "__main__":
- app = qt.QApplication([])
- w = qt.QTableView()
- d = numpy.random.normal(0, 1, (5, 1000, 1000))
- for i in range(5):
- d[i, :, :] += i * 10
- m = ArrayTableModel(data=d)
- w.setModel(m)
- m.setFrameIndex(3)
- # m.setArrayData(numpy.ones((100,)))
- w.show()
- app.exec_()
diff --git a/silx/gui/data/ArrayTableWidget.py b/silx/gui/data/ArrayTableWidget.py
deleted file mode 100644
index cb8e915..0000000
--- a/silx/gui/data/ArrayTableWidget.py
+++ /dev/null
@@ -1,492 +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.
-#
-# ###########################################################################*/
-"""This module defines a widget designed to display data arrays with any
-number of dimensions as 2D frames (images, slices) in a table view.
-The dimensions not displayed in the table can be browsed using improved
-sliders.
-
-The widget uses a TableView that relies on a custom abstract item
-model: :class:`silx.gui.data.ArrayTableModel`.
-"""
-from __future__ import division
-import sys
-
-from silx.gui import qt
-from silx.gui.widgets.TableWidget import TableView
-from .ArrayTableModel import ArrayTableModel
-from silx.gui.widgets.FrameBrowser import HorizontalSliderWithBrowser
-
-__authors__ = ["V.A. Sole", "P. Knobel"]
-__license__ = "MIT"
-__date__ = "24/01/2017"
-
-
-class AxesSelector(qt.QWidget):
- """Widget with two combo-boxes to select two dimensions among
- all possible dimensions of an n-dimensional array.
-
- The first combobox contains values from :math:`0` to :math:`n-2`.
-
- The choices in the 2nd CB depend on the value selected in the first one.
- If the value selected in the first CB is :math:`m`, the second one lets you
- select values from :math:`m+1` to :math:`n-1`.
-
- The two axes can be used to select the row axis and the column axis t
- display a slice of the array data in a table view.
- """
- sigDimensionsChanged = qt.Signal(int, int)
- """Signal emitted whenever one of the comboboxes is changed.
- The signal carries the two selected dimensions."""
-
- def __init__(self, parent=None, n=None):
- qt.QWidget.__init__(self, parent)
- self.layout = qt.QHBoxLayout(self)
- self.layout.setContentsMargins(0, 2, 0, 2)
- self.layout.setSpacing(10)
-
- self.rowsCB = qt.QComboBox(self)
- self.columnsCB = qt.QComboBox(self)
-
- self.layout.addWidget(qt.QLabel("Rows dimension", self))
- self.layout.addWidget(self.rowsCB)
- self.layout.addWidget(qt.QLabel(" ", self))
- self.layout.addWidget(qt.QLabel("Columns dimension", self))
- self.layout.addWidget(self.columnsCB)
- self.layout.addStretch(1)
-
- self._slotsAreConnected = False
- if n is not None:
- self.setNDimensions(n)
-
- def setNDimensions(self, n):
- """Initialize combo-boxes depending on number of dimensions of array.
- Initially, the rows dimension is the second-to-last one, and the
- columns dimension is the last one.
-
- Link the CBs together. MAke them emit a signal when their value is
- changed.
-
- :param int n: Number of dimensions of array
- """
- # remember the number of dimensions and the rows dimension
- self.n = n
- self._rowsDim = n - 2
-
- # ensure slots are disconnected before (re)initializing widget
- if self._slotsAreConnected:
- self.rowsCB.currentIndexChanged.disconnect(self._rowDimChanged)
- self.columnsCB.currentIndexChanged.disconnect(self._colDimChanged)
-
- self._clear()
- self.rowsCB.addItems([str(i) for i in range(n - 1)])
- self.rowsCB.setCurrentIndex(n - 2)
- if n >= 1:
- self.columnsCB.addItem(str(n - 1))
- self.columnsCB.setCurrentIndex(0)
-
- # reconnect slots
- self.rowsCB.currentIndexChanged.connect(self._rowDimChanged)
- self.columnsCB.currentIndexChanged.connect(self._colDimChanged)
- self._slotsAreConnected = True
-
- # emit new dimensions
- if n > 2:
- self.sigDimensionsChanged.emit(n - 2, n - 1)
-
- def setDimensions(self, row_dim, col_dim):
- """Set the rows and columns dimensions.
-
- The rows dimension must be lower than the columns dimension.
-
- :param int row_dim: Rows dimension
- :param int col_dim: Columns dimension
- """
- if row_dim >= col_dim:
- raise IndexError("Row dimension must be lower than column dimension")
- if not (0 <= row_dim < self.n - 1):
- raise IndexError("Row dimension must be between 0 and %d" % (self.n - 2))
- if not (row_dim < col_dim <= self.n - 1):
- raise IndexError("Col dimension must be between %d and %d" % (row_dim + 1, self.n - 1))
-
- # set the rows dimension; this triggers an update of columnsCB
- self.rowsCB.setCurrentIndex(row_dim)
- # columnsCB first item is "row_dim + 1". So index of "col_dim" is
- # col_dim - (row_dim + 1)
- self.columnsCB.setCurrentIndex(col_dim - row_dim - 1)
-
- def getDimensions(self):
- """Return a 2-tuple of the rows dimension and the columns dimension.
-
- :return: 2-tuple of axes numbers (row_dimension, col_dimension)
- """
- return self._getRowDim(), self._getColDim()
-
- def _clear(self):
- """Empty the combo-boxes"""
- self.rowsCB.clear()
- self.columnsCB.clear()
-
- def _getRowDim(self):
- """Get rows dimension, selected in :attr:`rowsCB`
- """
- # rows combobox contains elements "0", ..."n-2",
- # so the selected dim is always equal to the index
- return self.rowsCB.currentIndex()
-
- def _getColDim(self):
- """Get columns dimension, selected in :attr:`columnsCB`"""
- # columns combobox contains elements "row_dim+1", "row_dim+2", ..., "n-1"
- # so the selected dim is equal to row_dim + 1 + index
- return self._rowsDim + 1 + self.columnsCB.currentIndex()
-
- def _rowDimChanged(self):
- """Update columns combobox when the rows dimension is changed.
-
- Emit :attr:`sigDimensionsChanged`"""
- old_col_dim = self._getColDim()
- new_row_dim = self._getRowDim()
-
- # clear cols CB
- self.columnsCB.currentIndexChanged.disconnect(self._colDimChanged)
- self.columnsCB.clear()
- # refill cols CB
- for i in range(new_row_dim + 1, self.n):
- self.columnsCB.addItem(str(i))
-
- # keep previous col dimension if possible
- new_col_cb_idx = old_col_dim - (new_row_dim + 1)
- if new_col_cb_idx < 0:
- # if row_dim is now greater than the previous col_dim,
- # we select a new col_dim = row_dim + 1 (first element in cols CB)
- new_col_cb_idx = 0
- self.columnsCB.setCurrentIndex(new_col_cb_idx)
-
- # reconnect slot
- self.columnsCB.currentIndexChanged.connect(self._colDimChanged)
-
- self._rowsDim = new_row_dim
-
- self.sigDimensionsChanged.emit(self._getRowDim(), self._getColDim())
-
- def _colDimChanged(self):
- """Emit :attr:`sigDimensionsChanged`"""
- self.sigDimensionsChanged.emit(self._getRowDim(), self._getColDim())
-
-
-def _get_shape(array_like):
- """Return shape of an array like object.
-
- In case the object is a nested sequence (list of lists, tuples...),
- the size of each dimension is assumed to be uniform, and is deduced from
- the length of the first sequence.
-
- :param array_like: Array like object: numpy array, hdf5 dataset,
- multi-dimensional sequence
- :return: Shape of array, as a tuple of integers
- """
- if hasattr(array_like, "shape"):
- return array_like.shape
-
- shape = []
- subsequence = array_like
- while hasattr(subsequence, "__len__"):
- shape.append(len(subsequence))
- subsequence = subsequence[0]
-
- return tuple(shape)
-
-
-class ArrayTableWidget(qt.QWidget):
- """This widget is designed to display data of 2D frames (images, slices)
- in a table view. The widget can load any n-dimensional array, and display
- any 2-D frame/slice in the array.
-
- The index of the dimensions orthogonal to the displayed frame can be set
- interactively using a browser widget (sliders, buttons and text entries).
-
- To set the data, use :meth:`setArrayData`.
- 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):
- """
-
- :param parent: parent QWidget
- :param labels: list of labels for each dimension of the array
- """
- qt.QWidget.__init__(self, parent)
- self.mainLayout = qt.QVBoxLayout(self)
- self.mainLayout.setContentsMargins(0, 0, 0, 0)
- self.mainLayout.setSpacing(0)
-
- self.browserContainer = qt.QWidget(self)
- self.browserLayout = qt.QGridLayout(self.browserContainer)
- self.browserLayout.setContentsMargins(0, 0, 0, 0)
- self.browserLayout.setSpacing(0)
-
- self._dimensionLabelsText = []
- """List of text labels sorted in the increasing order of the dimension
- they apply to."""
- self._browserLabels = []
- """List of QLabel widgets."""
- self._browserWidgets = []
- """List of HorizontalSliderWithBrowser widgets."""
-
- self.axesSelector = AxesSelector(self)
-
- self.view = TableView(self)
-
- self.mainLayout.addWidget(self.browserContainer)
- self.mainLayout.addWidget(self.axesSelector)
- self.mainLayout.addWidget(self.view)
-
- self.model = ArrayTableModel(self)
- self.view.setModel(self.model)
-
- def setArrayData(self, data, labels=None, copy=True, editable=False):
- """Set the data array. Update frame browsers and labels.
-
- :param data: Numpy array or similar object (e.g. nested sequence,
- h5py dataset...)
- :param labels: list of labels for each dimension of the array, or
- boolean ``True`` to use default labels ("dimension 0",
- "dimension 1", ...). `None` to disable labels (default).
- :param bool copy: If *True*, store a copy of *data* in the model. If
- *False*, store a reference to *data* if possible (only possible if
- *data* is a proper numpy array or an object that implements the
- same methods).
- :param bool editable: Flag to enable editing data. Default is *False*
- """
- self._data_shape = _get_shape(data)
-
- n_widgets = len(self._browserWidgets)
- n_dimensions = len(self._data_shape)
-
- # Reset text of labels
- self._dimensionLabelsText = []
- for i in range(n_dimensions):
- if labels in [True, 1]:
- label_text = "Dimension %d" % i
- elif labels is None or i >= len(labels):
- label_text = ""
- else:
- label_text = labels[i]
- self._dimensionLabelsText.append(label_text)
-
- # not enough widgets, create new ones (we need n_dim - 2)
- for i in range(n_widgets, n_dimensions - 2):
- browser = HorizontalSliderWithBrowser(self.browserContainer)
- self.browserLayout.addWidget(browser, i, 1)
- self._browserWidgets.append(browser)
- browser.valueChanged.connect(self._browserSlot)
- browser.setEnabled(False)
- browser.hide()
-
- label = qt.QLabel(self.browserContainer)
- self._browserLabels.append(label)
- self.browserLayout.addWidget(label, i, 0)
- label.hide()
-
- n_widgets = len(self._browserWidgets)
- for i in range(n_widgets):
- label = self._browserLabels[i]
- browser = self._browserWidgets[i]
-
- if (i + 2) < n_dimensions:
- label.setText(self._dimensionLabelsText[i])
- browser.setRange(0, self._data_shape[i] - 1)
- browser.setEnabled(True)
- browser.show()
- if labels is not None:
- label.show()
- else:
- label.hide()
- else:
- browser.setEnabled(False)
- browser.hide()
- label.hide()
-
- # set model
- self.model.setArrayData(data, copy=copy, editable=editable)
- # some linux distributions need this call
- self.view.setModel(self.model)
- if editable:
- self.view.enableCut()
- self.view.enablePaste()
-
- # initialize & connect axesSelector
- self.axesSelector.setNDimensions(n_dimensions)
- self.axesSelector.sigDimensionsChanged.connect(self.setFrameAxes)
-
- def setArrayColors(self, bgcolors=None, fgcolors=None):
- """Set the colors for all table cells by passing an array
- of RGB or RGBA values (integers between 0 and 255).
-
- The shape of the colors array must be consistent with the data shape.
-
- If the data array is n-dimensional, the colors array must be
- (n+1)-dimensional, with the first n-dimensions identical to the data
- array dimensions, and the last dimension length-3 (RGB) or
- length-4 (RGBA).
-
- :param bgcolors: RGB or RGBA colors array, defining the background color
- for each cell in the table.
- :param fgcolors: RGB or RGBA colors array, defining the foreground color
- (text color) for each cell in the table.
- """
- self.model.setArrayColors(bgcolors, fgcolors)
-
- def displayAxesSelector(self, isVisible):
- """Allow to display or hide the axes selector.
-
- :param bool isVisible: True to display the axes selector.
- """
- self.axesSelector.setVisible(isVisible)
-
- def setFrameIndex(self, index):
- """Set the active slice/image index in the n-dimensional array.
-
- A frame is a 2D array extracted from an array. This frame is
- necessarily parallel to 2 axes, and orthogonal to all other axes.
-
- The index of a frame is a sequence of indices along the orthogonal
- axes, where the frame intersects the respective axis. The indices
- are listed in the same order as the corresponding dimensions of the
- data array.
-
- For example, it the data array has 5 dimensions, and we are
- considering frames whose parallel axes are the 2nd and 4th dimensions
- of the array, the frame index will be a sequence of length 3
- corresponding to the indices where the frame intersects the 1st, 3rd
- and 5th axes.
-
- :param index: Sequence of indices defining the active data slice in
- a n-dimensional array. The sequence length is :math:`n-2`
- :raise: IndexError if any index in the index sequence is out of bound
- on its respective axis.
- """
- self.model.setFrameIndex(index)
-
- def _resetBrowsers(self, perspective):
- """Adjust limits for browsers based on the perspective and the
- size of the corresponding dimensions. Reset the index to 0.
- Update the dimension in the labels.
-
- :param perspective: Sequence of axes/dimensions numbers (0-based)
- defining the axes orthogonal to the frame.
- """
- # for 3D arrays we can accept an int rather than a 1-tuple
- if not hasattr(perspective, "__len__"):
- perspective = [perspective]
-
- # perspective must be sorted
- perspective = sorted(perspective)
-
- n_dimensions = len(self._data_shape)
- for i in range(n_dimensions - 2):
- browser = self._browserWidgets[i]
- label = self._browserLabels[i]
- browser.setRange(0, self._data_shape[perspective[i]] - 1)
- browser.setValue(0)
- label.setText(self._dimensionLabelsText[perspective[i]])
-
- def setPerspective(self, perspective):
- """Set the *perspective* by specifying which axes are orthogonal
- to the frame.
-
- For the opposite approach (defining parallel axes), use
- :meth:`setFrameAxes` instead.
-
- :param perspective: Sequence of unique axes numbers (0-based) defining
- the orthogonal axes. For a n-dimensional array, the sequence
- length is :math:`n-2`. The order is of the sequence is not taken
- into account (the dimensions are displayed in increasing order
- in the widget).
- """
- self.model.setPerspective(perspective)
- self._resetBrowsers(perspective)
-
- def setFrameAxes(self, row_axis, col_axis):
- """Set the *perspective* by specifying which axes are parallel
- to the frame.
-
- For the opposite approach (defining orthogonal axes), use
- :meth:`setPerspective` instead.
-
- :param int row_axis: Index (0-based) of the first dimension used as a frame
- axis
- :param int col_axis: Index (0-based) of the 2nd dimension used as a frame
- axis
- """
- self.model.setFrameAxes(row_axis, col_axis)
- n_dimensions = len(self._data_shape)
- perspective = tuple(set(range(0, n_dimensions)) - {row_axis, col_axis})
- self._resetBrowsers(perspective)
-
- def _browserSlot(self, value):
- index = []
- for browser in self._browserWidgets:
- if browser.isEnabled():
- index.append(browser.value())
- self.setFrameIndex(index)
- self.view.reset()
-
- def getData(self, copy=True):
- """Return a copy of the data array, or a reference to it if
- *copy=False* is passed as parameter.
-
- :param bool copy: If *True* (default), return a copy of the data. If
- *False*, return a reference.
- :return: Numpy array of data, or reference to original data object
- if *copy=False*
- """
- return self.model.getData(copy=copy)
-
-
-def main():
- import numpy
- a = qt.QApplication([])
- d = numpy.random.normal(0, 1, (4, 5, 1000, 1000))
- for j in range(4):
- for i in range(5):
- d[j, i, :, :] += i + 10 * j
- w = ArrayTableWidget()
- if "2" in sys.argv:
- print("sending a single image")
- w.setArrayData(d[0, 0])
- elif "3" in sys.argv:
- print("sending 5 images")
- w.setArrayData(d[0])
- else:
- print("sending 4 * 5 images ")
- w.setArrayData(d, labels=True)
- w.show()
- a.exec_()
-
-if __name__ == "__main__":
- main()
diff --git a/silx/gui/data/DataViewer.py b/silx/gui/data/DataViewer.py
deleted file mode 100644
index 2e51439..0000000
--- a/silx/gui/data/DataViewer.py
+++ /dev/null
@@ -1,593 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016-2019 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""This module defines a widget designed to display data using the most adapted
-view from the ones provided by silx.
-"""
-from __future__ import division
-
-import logging
-import os.path
-import collections
-from silx.gui import qt
-from silx.gui.data import DataViews
-from silx.gui.data.DataViews import _normalizeData
-from silx.gui.utils import blockSignals
-from silx.gui.data.NumpyAxesSelector import NumpyAxesSelector
-
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "12/02/2019"
-
-
-_logger = logging.getLogger(__name__)
-
-
-DataSelection = collections.namedtuple("DataSelection",
- ["filename", "datapath",
- "slice", "permutation"])
-
-
-class DataViewer(qt.QFrame):
- """Widget to display any kind of data
-
- .. image:: img/DataViewer.png
-
- The method :meth:`setData` allows to set any data to the widget. Mostly
- `numpy.array` and `h5py.Dataset` are supported with adapted views. Other
- data types are displayed using a text viewer.
-
- A default view is automatically selected when a data is set. The method
- :meth:`setDisplayMode` allows to change the view. To have a graphical tool
- to select the view, prefer using the widget :class:`DataViewerFrame`.
-
- The dimension of the input data and the expected dimension of the selected
- view can differ. For example you can display an image (2D) from 4D
- data. In this case a :class:`NumpyAxesSelector` is displayed to allow the
- user to select the axis mapping and the slicing of other axes.
-
- .. code-block:: python
-
- import numpy
- data = numpy.random.rand(500,500)
- viewer = DataViewer()
- viewer.setData(data)
- viewer.setVisible(True)
- """
-
- displayedViewChanged = qt.Signal(object)
- """Emitted when the displayed view changes"""
-
- dataChanged = qt.Signal()
- """Emitted when the data changes"""
-
- currentAvailableViewsChanged = qt.Signal()
- """Emitted when the current available views (which support the current
- data) change"""
-
- def __init__(self, parent=None):
- """Constructor
-
- :param QWidget parent: The parent of the widget
- """
- super(DataViewer, self).__init__(parent)
-
- self.__stack = qt.QStackedWidget(self)
- self.__numpySelection = NumpyAxesSelector(self)
- self.__numpySelection.selectedAxisChanged.connect(self.__numpyAxisChanged)
- self.__numpySelection.selectionChanged.connect(self.__numpySelectionChanged)
- self.__numpySelection.customAxisChanged.connect(self.__numpyCustomAxisChanged)
-
- self.setLayout(qt.QVBoxLayout(self))
- self.layout().addWidget(self.__stack, 1)
-
- group = qt.QGroupBox(self)
- group.setLayout(qt.QVBoxLayout())
- group.layout().addWidget(self.__numpySelection)
- group.setTitle("Axis selection")
- self.__axisSelection = group
-
- self.layout().addWidget(self.__axisSelection)
-
- self.__currentAvailableViews = []
- self.__currentView = None
- self.__data = None
- self.__info = None
- self.__useAxisSelection = False
- self.__userSelectedView = None
- self.__hooks = None
-
- self.__views = []
- self.__index = {}
- """store stack index for each views"""
-
- self._initializeViews()
-
- def _initializeViews(self):
- """Inisialize the available views"""
- views = self.createDefaultViews(self.__stack)
- self.__views = list(views)
- self.setDisplayMode(DataViews.EMPTY_MODE)
-
- def setGlobalHooks(self, hooks):
- """Set a data view hooks for all the views
-
- :param DataViewHooks context: The hooks to use
- """
- self.__hooks = hooks
- for v in self.__views:
- v.setHooks(hooks)
-
- def createDefaultViews(self, parent=None):
- """Create and returns available views which can be displayed by default
- by the data viewer. It is called internally by the widget. It can be
- overwriten to provide a different set of viewers.
-
- :param QWidget parent: QWidget parent of the views
- :rtype: List[silx.gui.data.DataViews.DataView]
- """
- viewClasses = [
- DataViews._EmptyView,
- DataViews._Hdf5View,
- DataViews._NXdataView,
- DataViews._Plot1dView,
- DataViews._ImageView,
- DataViews._Plot3dView,
- DataViews._RawView,
- DataViews._StackView,
- DataViews._Plot2dRecordView,
- ]
- views = []
- for viewClass in viewClasses:
- try:
- view = viewClass(parent)
- views.append(view)
- except Exception:
- _logger.warning("%s instantiation failed. View is ignored" % viewClass.__name__)
- _logger.debug("Backtrace", exc_info=True)
-
- return views
-
- def clear(self):
- """Clear the widget"""
- self.setData(None)
-
- def normalizeData(self, data):
- """Returns a normalized data if the embed a numpy or a dataset.
- Else returns the data."""
- return _normalizeData(data)
-
- def __getStackIndex(self, view):
- """Get the stack index containing the view.
-
- :param silx.gui.data.DataViews.DataView view: The view
- """
- if view not in self.__index:
- widget = view.getWidget()
- index = self.__stack.addWidget(widget)
- self.__index[view] = index
- else:
- index = self.__index[view]
- return index
-
- def __clearCurrentView(self):
- """Clear the current selected view"""
- view = self.__currentView
- if view is not None:
- view.clear()
-
- def __numpyCustomAxisChanged(self, name, value):
- view = self.__currentView
- if view is not None:
- view.setCustomAxisValue(name, value)
-
- def __updateNumpySelectionAxis(self):
- """
- Update the numpy-selector according to the needed axis names
- """
- with blockSignals(self.__numpySelection):
- previousPermutation = self.__numpySelection.permutation()
- previousSelection = self.__numpySelection.selection()
-
- self.__numpySelection.clear()
-
- info = self._getInfo()
- axisNames = self.__currentView.axesNames(self.__data, info)
- if (info.isArray and info.size != 0 and
- self.__data is not None and axisNames is not None):
- self.__useAxisSelection = True
- self.__numpySelection.setAxisNames(axisNames)
- self.__numpySelection.setCustomAxis(
- self.__currentView.customAxisNames())
- data = self.normalizeData(self.__data)
- self.__numpySelection.setData(data)
-
- # Try to restore previous permutation and selection
- try:
- self.__numpySelection.setSelection(
- previousSelection, previousPermutation)
- except ValueError as e:
- _logger.info("Not restoring selection because: %s", e)
-
- if hasattr(data, "shape"):
- isVisible = not (len(axisNames) == 1 and len(data.shape) == 1)
- else:
- isVisible = True
- self.__axisSelection.setVisible(isVisible)
- else:
- self.__useAxisSelection = False
- self.__axisSelection.setVisible(False)
-
- def __updateDataInView(self):
- """
- Update the views using the current data
- """
- if self.__useAxisSelection:
- self.__displayedData = self.__numpySelection.selectedData()
-
- permutation = self.__numpySelection.permutation()
- normal = tuple(range(len(permutation)))
- if permutation == normal:
- permutation = None
- slicing = self.__numpySelection.selection()
- normal = tuple([slice(None)] * len(slicing))
- if slicing == normal:
- slicing = None
- else:
- self.__displayedData = self.__data
- permutation = None
- slicing = None
-
- try:
- filename = os.path.abspath(self.__data.file.filename)
- except:
- filename = None
-
- try:
- datapath = self.__data.name
- except:
- datapath = None
-
- # FIXME: maybe use DataUrl, with added support of permutation
- self.__displayedSelection = DataSelection(filename, datapath, slicing, permutation)
-
- # TODO: would be good to avoid that, it should be synchonous
- qt.QTimer.singleShot(10, self.__setDataInView)
-
- def __setDataInView(self):
- self.__currentView.setData(self.__displayedData)
- self.__currentView.setDataSelection(self.__displayedSelection)
-
- def setDisplayedView(self, view):
- """Set the displayed view.
-
- Change the displayed view according to the view itself.
-
- :param silx.gui.data.DataViews.DataView view: The DataView to use to display the data
- """
- self.__userSelectedView = view
- self._setDisplayedView(view)
-
- def _setDisplayedView(self, view):
- """Internal set of the displayed view.
-
- Change the displayed view according to the view itself.
-
- :param silx.gui.data.DataViews.DataView view: The DataView to use to display the data
- """
- if self.__currentView is view:
- return
- self.__clearCurrentView()
- self.__currentView = view
- self.__updateNumpySelectionAxis()
- self.__updateDataInView()
- stackIndex = self.__getStackIndex(self.__currentView)
- if self.__currentView is not None:
- self.__currentView.select()
- self.__stack.setCurrentIndex(stackIndex)
- self.displayedViewChanged.emit(view)
-
- def getViewFromModeId(self, modeId):
- """Returns the first available view which have the requested modeId.
- Return None if modeId does not correspond to an existing view.
-
- :param int modeId: Requested mode id
- :rtype: silx.gui.data.DataViews.DataView
- """
- for view in self.__views:
- if view.modeId() == modeId:
- return view
- return None
-
- def setDisplayMode(self, modeId):
- """Set the displayed view using display mode.
-
- Change the displayed view according to the requested mode.
-
- :param int modeId: Display mode, one of
-
- - `DataViews.EMPTY_MODE`: display nothing
- - `DataViews.PLOT1D_MODE`: display the data as a curve
- - `DataViews.IMAGE_MODE`: display the data as an image
- - `DataViews.PLOT3D_MODE`: display the data as an isosurface
- - `DataViews.RAW_MODE`: display the data as a table
- - `DataViews.STACK_MODE`: display the data as a stack of images
- - `DataViews.HDF5_MODE`: display the data as a table of HDF5 info
- - `DataViews.NXDATA_MODE`: display the data as NXdata
- """
- try:
- view = self.getViewFromModeId(modeId)
- except KeyError:
- raise ValueError("Display mode %s is unknown" % modeId)
- self._setDisplayedView(view)
-
- def displayedView(self):
- """Returns the current displayed view.
-
- :rtype: silx.gui.data.DataViews.DataView
- """
- return self.__currentView
-
- def addView(self, view):
- """Allow to add a view to the dataview.
-
- If the current data support this view, it will be displayed.
-
- :param DataView view: A dataview
- """
- if self.__hooks is not None:
- view.setHooks(self.__hooks)
- self.__views.append(view)
- # TODO It can be skipped if the view do not support the data
- self.__updateAvailableViews()
-
- def removeView(self, view):
- """Allow to remove a view which was available from the dataview.
-
- If the view was displayed, the widget will be updated.
-
- :param DataView view: A dataview
- """
- self.__views.remove(view)
- self.__stack.removeWidget(view.getWidget())
- # invalidate the full index. It will be updated as expected
- self.__index = {}
-
- if self.__userSelectedView is view:
- self.__userSelectedView = None
-
- if view is self.__currentView:
- self.__updateView()
- else:
- # TODO It can be skipped if the view is not part of the
- # available views
- self.__updateAvailableViews()
-
- def __updateAvailableViews(self):
- """
- Update available views from the current data.
- """
- data = self.__data
- info = self._getInfo()
- # sort available views according to priority
- views = []
- for v in self.__views:
- views.extend(v.getMatchingViews(data, info))
- views = [(v.getCachedDataPriority(data, info), v) for v in views]
- views = filter(lambda t: t[0] > DataViews.DataView.UNSUPPORTED, views)
- views = sorted(views, reverse=True)
- views = [v[1] for v in views]
-
- # store available views
- self.__setCurrentAvailableViews(views)
-
- def __updateView(self):
- """Display the data using the widget which fit the best"""
- data = self.__data
-
- # update available views for this data
- self.__updateAvailableViews()
- available = self.__currentAvailableViews
-
- # display the view with the most priority (the default view)
- view = self.getDefaultViewFromAvailableViews(data, available)
- self.__clearCurrentView()
- try:
- self._setDisplayedView(view)
- except Exception as e:
- # in case there is a problem to read the data, try to use a safe
- # view
- view = self.getSafeViewFromAvailableViews(data, available)
- self._setDisplayedView(view)
- raise e
-
- def getSafeViewFromAvailableViews(self, data, available):
- """Returns a view which is sure to display something without failing
- on rendering.
-
- :param object data: data which will be displayed
- :param List[view] available: List of available views, from highest
- priority to lowest.
- :rtype: DataView
- """
- hdf5View = self.getViewFromModeId(DataViews.HDF5_MODE)
- if hdf5View in available:
- return hdf5View
- return self.getViewFromModeId(DataViews.EMPTY_MODE)
-
- def getDefaultViewFromAvailableViews(self, data, available):
- """Returns the default view which will be used according to available
- views.
-
- :param object data: data which will be displayed
- :param List[view] available: List of available views, from highest
- priority to lowest.
- :rtype: DataView
- """
- if len(available) > 0:
- # returns the view with the highest priority
- if self.__userSelectedView in available:
- return self.__userSelectedView
- self.__userSelectedView = None
- view = available[0]
- else:
- # else returns the empty view
- view = self.getViewFromModeId(DataViews.EMPTY_MODE)
- return view
-
- def __setCurrentAvailableViews(self, availableViews):
- """Set the current available viewa
-
- :param List[DataView] availableViews: Current available viewa
- """
- self.__currentAvailableViews = availableViews
- self.currentAvailableViewsChanged.emit()
-
- def currentAvailableViews(self):
- """Returns the list of available views for the current data
-
- :rtype: List[DataView]
- """
- return self.__currentAvailableViews
-
- def getReachableViews(self):
- """Returns the list of reachable views from the registred available
- views.
-
- :rtype: List[DataView]
- """
- views = []
- for v in self.availableViews():
- views.extend(v.getReachableViews())
- return views
-
- def availableViews(self):
- """Returns the list of registered views
-
- :rtype: List[DataView]
- """
- return self.__views
-
- def setData(self, data):
- """Set the data to view.
-
- It mostly can be a h5py.Dataset or a numpy.ndarray. Other kind of
- objects will be displayed as text rendering.
-
- :param numpy.ndarray data: The data.
- """
- self.__data = data
- self._invalidateInfo()
- self.__displayedData = None
- self.__displayedSelection = None
- self.__updateView()
- self.__updateNumpySelectionAxis()
- self.__updateDataInView()
- self.dataChanged.emit()
-
- def __numpyAxisChanged(self):
- """
- Called when axis selection of the numpy-selector changed
- """
- self.__clearCurrentView()
-
- def __numpySelectionChanged(self):
- """
- Called when data selection of the numpy-selector changed
- """
- self.__updateDataInView()
-
- def data(self):
- """Returns the data"""
- return self.__data
-
- def _invalidateInfo(self):
- """Invalidate DataInfo cache."""
- self.__info = None
-
- def _getInfo(self):
- """Returns the DataInfo of the current selected data.
-
- This value is cached.
-
- :rtype: DataInfo
- """
- if self.__info is None:
- self.__info = DataViews.DataInfo(self.__data)
- return self.__info
-
- def displayMode(self):
- """Returns the current display mode"""
- return self.__currentView.modeId()
-
- def replaceView(self, modeId, newView):
- """Replace one of the builtin data views with a custom view.
- Return True in case of success, False in case of failure.
-
- .. note::
-
- This method must be called just after instantiation, before
- the viewer is used.
-
- :param int modeId: Unique mode ID identifying the DataView to
- be replaced. One of:
-
- - `DataViews.EMPTY_MODE`
- - `DataViews.PLOT1D_MODE`
- - `DataViews.IMAGE_MODE`
- - `DataViews.PLOT2D_MODE`
- - `DataViews.COMPLEX_IMAGE_MODE`
- - `DataViews.PLOT3D_MODE`
- - `DataViews.RAW_MODE`
- - `DataViews.STACK_MODE`
- - `DataViews.HDF5_MODE`
- - `DataViews.NXDATA_MODE`
- - `DataViews.NXDATA_INVALID_MODE`
- - `DataViews.NXDATA_SCALAR_MODE`
- - `DataViews.NXDATA_CURVE_MODE`
- - `DataViews.NXDATA_XYVSCATTER_MODE`
- - `DataViews.NXDATA_IMAGE_MODE`
- - `DataViews.NXDATA_STACK_MODE`
-
- :param DataViews.DataView newView: New data view
- :return: True if replacement was successful, else False
- """
- assert isinstance(newView, DataViews.DataView)
- isReplaced = False
- for idx, view in enumerate(self.__views):
- if view.modeId() == modeId:
- if self.__hooks is not None:
- newView.setHooks(self.__hooks)
- self.__views[idx] = newView
- isReplaced = True
- break
- elif isinstance(view, DataViews.CompositeDataView):
- isReplaced = view.replaceView(modeId, newView)
- if isReplaced:
- break
-
- if isReplaced:
- self.__updateAvailableViews()
- return isReplaced
diff --git a/silx/gui/data/DataViewerFrame.py b/silx/gui/data/DataViewerFrame.py
deleted file mode 100644
index 9bfb95b..0000000
--- a/silx/gui/data/DataViewerFrame.py
+++ /dev/null
@@ -1,217 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016-2018 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""This module contains a DataViewer with a view selector.
-"""
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "12/02/2019"
-
-from silx.gui import qt
-from .DataViewer import DataViewer
-from .DataViewerSelector import DataViewerSelector
-
-
-class DataViewerFrame(qt.QWidget):
- """
- A :class:`DataViewer` with a view selector.
-
- .. image:: img/DataViewerFrame.png
-
- This widget provides the same API as :class:`DataViewer`. Therefore, for more
- documentation, take a look at the documentation of the class
- :class:`DataViewer`.
-
- .. code-block:: python
-
- import numpy
- data = numpy.random.rand(500,500)
- viewer = DataViewerFrame()
- viewer.setData(data)
- viewer.setVisible(True)
-
- """
-
- displayedViewChanged = qt.Signal(object)
- """Emitted when the displayed view changes"""
-
- dataChanged = qt.Signal()
- """Emitted when the data changes"""
-
- def __init__(self, parent=None):
- """
- Constructor
-
- :param qt.QWidget parent:
- """
- super(DataViewerFrame, self).__init__(parent)
-
- class _DataViewer(DataViewer):
- """Overwrite methods to avoid to create views while the instance
- is not created. `initializeViews` have to be called manually."""
-
- def _initializeViews(self):
- pass
-
- def initializeViews(self):
- """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()
- self.__dataViewer.setFrameShape(qt.QFrame.StyledPanel)
- self.__dataViewer.setFrameShadow(qt.QFrame.Sunken)
- self.__dataViewerSelector = DataViewerSelector(self, self.__dataViewer)
- self.__dataViewerSelector.setFlat(True)
-
- layout = qt.QVBoxLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(0)
- layout.addWidget(self.__dataViewer, 1)
- layout.addWidget(self.__dataViewerSelector)
- self.setLayout(layout)
-
- self.__dataViewer.dataChanged.connect(self.__dataChanged)
- self.__dataViewer.displayedViewChanged.connect(self.__displayedViewChanged)
-
- def __dataChanged(self):
- """Called when the data is changed"""
- self.dataChanged.emit()
-
- def __displayedViewChanged(self, view):
- """Called when the displayed view changes"""
- self.displayedViewChanged.emit(view)
-
- def setGlobalHooks(self, hooks):
- """Set a data view hooks for all the views
-
- :param DataViewHooks context: The hooks to use
- """
- self.__dataViewer.setGlobalHooks(hooks)
-
- def getReachableViews(self):
- return self.__dataViewer.getReachableViews()
-
- def availableViews(self):
- """Returns the list of registered views
-
- :rtype: List[DataView]
- """
- return self.__dataViewer.availableViews()
-
- def currentAvailableViews(self):
- """Returns the list of available views for the current data
-
- :rtype: List[DataView]
- """
- return self.__dataViewer.currentAvailableViews()
-
- def createDefaultViews(self, parent=None):
- """Create and returns available views which can be displayed by default
- by the data viewer. It is called internally by the widget. It can be
- overwriten to provide a different set of viewers.
-
- :param QWidget parent: QWidget parent of the views
- :rtype: List[silx.gui.data.DataViews.DataView]
- """
- return self.__dataViewer._createDefaultViews(parent)
-
- def addView(self, view):
- """Allow to add a view to the dataview.
-
- If the current data support this view, it will be displayed.
-
- :param DataView view: A dataview
- """
- return self.__dataViewer.addView(view)
-
- def removeView(self, view):
- """Allow to remove a view which was available from the dataview.
-
- If the view was displayed, the widget will be updated.
-
- :param DataView view: A dataview
- """
- return self.__dataViewer.removeView(view)
-
- def setData(self, data):
- """Set the data to view.
-
- It mostly can be a h5py.Dataset or a numpy.ndarray. Other kind of
- objects will be displayed as text rendering.
-
- :param numpy.ndarray data: The data.
- """
- self.__dataViewer.setData(data)
-
- def data(self):
- """Returns the data"""
- return self.__dataViewer.data()
-
- def setDisplayedView(self, view):
- self.__dataViewer.setDisplayedView(view)
-
- def displayedView(self):
- return self.__dataViewer.displayedView()
-
- def displayMode(self):
- return self.__dataViewer.displayMode()
-
- def setDisplayMode(self, modeId):
- """Set the displayed view using display mode.
-
- Change the displayed view according to the requested mode.
-
- :param int modeId: Display mode, one of
-
- - `EMPTY_MODE`: display nothing
- - `PLOT1D_MODE`: display the data as a curve
- - `PLOT2D_MODE`: display the data as an image
- - `TEXT_MODE`: display the data as a text
- - `ARRAY_MODE`: display the data as a table
- """
- return self.__dataViewer.setDisplayMode(modeId)
-
- def getViewFromModeId(self, modeId):
- """See :meth:`DataViewer.getViewFromModeId`"""
- return self.__dataViewer.getViewFromModeId(modeId)
-
- def replaceView(self, modeId, newView):
- """Replace one of the builtin data views with a custom view.
- See :meth:`DataViewer.replaceView` for more documentation.
-
- :param DataViews.DataView newView: New data view
- :return: True if replacement was successful, else False
- """
- return self.__dataViewer.replaceView(modeId, newView)
diff --git a/silx/gui/data/DataViewerSelector.py b/silx/gui/data/DataViewerSelector.py
deleted file mode 100644
index a1e9947..0000000
--- a/silx/gui/data/DataViewerSelector.py
+++ /dev/null
@@ -1,175 +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.
-#
-# ###########################################################################*/
-"""This module defines a widget to be able to select the available view
-of the DataViewer.
-"""
-from __future__ import division
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "12/02/2019"
-
-import weakref
-import functools
-from silx.gui import qt
-import silx.utils.weakref
-
-
-class DataViewerSelector(qt.QWidget):
- """Widget to be able to select a custom view from the DataViewer"""
-
- def __init__(self, parent=None, dataViewer=None):
- """Constructor
-
- :param QWidget parent: The parent of the widget
- :param DataViewer dataViewer: The connected `DataViewer`
- """
- super(DataViewerSelector, self).__init__(parent)
-
- self.__group = None
- self.__buttons = {}
- self.__buttonLayout = None
- self.__buttonDummy = None
- self.__dataViewer = None
-
- # Create the fixed layout
- self.setLayout(qt.QHBoxLayout())
- layout = self.layout()
- layout.setContentsMargins(0, 0, 0, 0)
- self.__buttonLayout = qt.QHBoxLayout()
- self.__buttonLayout.setContentsMargins(0, 0, 0, 0)
- layout.addLayout(self.__buttonLayout)
- layout.addStretch(1)
-
- if dataViewer is not None:
- self.setDataViewer(dataViewer)
-
- def __updateButtons(self):
- if self.__group is not None:
- self.__group.deleteLater()
-
- # Clean up
- for _, b in self.__buttons.items():
- b.deleteLater()
- if self.__buttonDummy is not None:
- self.__buttonDummy.deleteLater()
- self.__buttonDummy = None
- self.__buttons = {}
- self.__buttonDummy = None
-
- self.__group = qt.QButtonGroup(self)
- if self.__dataViewer is None:
- return
-
- iconSize = qt.QSize(16, 16)
-
- for view in self.__dataViewer.getReachableViews():
- label = view.label()
- icon = view.icon()
- button = qt.QPushButton(label)
- button.setIcon(icon)
- button.setIconSize(iconSize)
- button.setCheckable(True)
- # the weak objects are needed to be able to destroy the widget safely
- weakView = weakref.ref(view)
- weakMethod = silx.utils.weakref.WeakMethodProxy(self.__setDisplayedView)
- callback = functools.partial(weakMethod, weakView)
- button.clicked.connect(callback)
- self.__buttonLayout.addWidget(button)
- self.__group.addButton(button)
- self.__buttons[view] = button
-
- button = qt.QPushButton("Dummy")
- button.setCheckable(True)
- button.setVisible(False)
- self.__buttonLayout.addWidget(button)
- self.__group.addButton(button)
- self.__buttonDummy = button
-
- self.__updateButtonsVisibility()
- self.__displayedViewChanged(self.__dataViewer.displayedView())
-
- def setDataViewer(self, dataViewer):
- """Define the dataviewer connected to this status bar
-
- :param DataViewer dataViewer: The connected `DataViewer`
- """
- if self.__dataViewer is dataViewer:
- return
- if self.__dataViewer is not None:
- self.__dataViewer.dataChanged.disconnect(self.__updateButtonsVisibility)
- self.__dataViewer.displayedViewChanged.disconnect(self.__displayedViewChanged)
- self.__dataViewer = dataViewer
- if self.__dataViewer is not None:
- self.__dataViewer.dataChanged.connect(self.__updateButtonsVisibility)
- self.__dataViewer.displayedViewChanged.connect(self.__displayedViewChanged)
- self.__updateButtons()
-
- def setFlat(self, isFlat):
- """Set the flat state of all the buttons.
-
- :param bool isFlat: True to display the buttons flatten.
- """
- for b in self.__buttons.values():
- b.setFlat(isFlat)
- self.__buttonDummy.setFlat(isFlat)
-
- def __displayedViewChanged(self, view):
- """Called on displayed view changes"""
- selectedButton = self.__buttons.get(view, self.__buttonDummy)
- selectedButton.setChecked(True)
-
- def __setDisplayedView(self, refView, clickEvent=None):
- """Display a data using the requested view
-
- :param DataView view: Requested view
- :param clickEvent: Event sent by the clicked event
- """
- if self.__dataViewer is None:
- return
- view = refView()
- if view is None:
- return
- self.__dataViewer.setDisplayedView(view)
-
- def __checkAvailableButtons(self):
- views = set(self.__dataViewer.getReachableViews())
- if views == set(self.__buttons.keys()):
- return
- # Recreate all the buttons
- # TODO: We dont have to create everything again
- # We expect the views stay quite stable
- self.__updateButtons()
-
- def __updateButtonsVisibility(self):
- """Called on data changed"""
- if self.__dataViewer is None:
- for b in self.__buttons.values():
- b.setVisible(False)
- else:
- self.__checkAvailableButtons()
- availableViews = set(self.__dataViewer.currentAvailableViews())
- for view, button in self.__buttons.items():
- button.setVisible(view in availableViews)
diff --git a/silx/gui/data/DataViews.py b/silx/gui/data/DataViews.py
deleted file mode 100644
index b18a813..0000000
--- a/silx/gui/data/DataViews.py
+++ /dev/null
@@ -1,2059 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016-2020 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